英文原文:http://docs.scala-lang.org/style/
译文如下:
一、类
Class / Object / Trait构造函数应该全部声明为一行,除非该行变为“太长”(大约100个字符)。在这种情况下,将每个构造函数参数放在自己的行上,缩进四个空格:
class Person(name: String, age: Int) {
}
如果一个类/对象/特征扩展任何东西,则应用相同的一般规则,将其放在一行上,除非它超过约100个字符,然后缩进四个空格,每个项目在其自己的行上,两个 空格用于扩展; 这提供了构造函数参数和扩展之间的视觉分隔:
class Person(
name: String,
age: Int,
birthdate: Date,
astrologicalSign: String,
shoeSize: Int,
favoriteColor: java.awt.Color)
extends Entity
with Logging
with Identifiable
with Serializable {
}
(一)、排序类元素
所有类/对象/特征成员都应该被声明为换行符。这个规则的唯一例外是var
和val
。这些可能没有介入的换行符,但是只有当没有一个字段具有Scaladoc,并且所有字段都有简单(最多20个ish字符,一行)定义时:
class Foo {
val bar = 42
val baz = "Daniel"
字段应位于范围中的方法之前。唯一的例外是如果 val
具有块定义(多于一个表达式)并执行可被视为“类似方法”的操作(例如,计算a的长度List
)。在这种情况下,val
按照逻辑成员排序将要求,非平凡可以在文件的稍后点被声明。这条规则只适用于val
和lazy val
!如果var
声明遍布整个类文件,变得很难跟踪变化的别名。
(二)、方法
方法应按照以下格式声明:
def foo(bar: Baz): Bin = expr
具有默认参数值的方法应以类似的方式声明,等号两边的空格:
def foo(x: Int = 6, y: Int = 7): Int = x + y
您应该为所有公共成员指定返回类型。考虑编译器检查的文档。它还有助于在面对不断变化的类型推断时保持二进制兼容性(如果推断方法实现的更改可能传播到返回类型)。
本地方法或私有方法可以省略它们的返回类型:
private def foo(x: Int = 6, y: Int = 7) = x + y
1、程序语法
避免使用程序语法,因为它很容易引起混淆,很简单。
// don't do this
def printBar(bar: Baz) {
println(bar)
}
2、修饰符
方法修饰符应按以下顺序给出(每个适用时):
- 注释,每个都在自己的线上
- 覆盖修饰符(
override
) - 访问修饰符(
protected
,private
) - 最终修饰符(
final
) - def
@Transaction
@throws(classOf[IOException])
override protected final def foo() {
...
}
3、主体
当方法体包含小于30(或更多)个字符的单个表达式时,应在单行上给出以下方法:
def add(a: Int, b: Int): Int = a + b
当方法主体是一个长于 30(或更多)字符但仍小于70(或更多)字符的单个表达式时,应在以下行中给出缩进两个空格:
def sum(ls: List[String]): Int =
ls.map(_.toInt).foldLeft(0)(_ + _)
这两种情况的区别是有些人为的。一般来说,您应该根据具体情况选择更易读的风格。例如,您的方法声明可能很长,而表达体可能相当短。在这种情况下,将表达式放在下一行可能更可读,而不是使声明行太长。
当一种方法的身体不能简单地表达在一条线上或具有非功能性(某些可变状态,局部或其他方式)时,身体必须用大括号括起来:
def sum(ls: List[String]): Int = {
val ints = ls map (_.toInt)
ints.foldLeft(0)(_ + _)
}
包含单个match
表达式的方法应以以下方式声明:
// right!
def sum(ls: List[Int]): Int = ls match {
case hd :: tail => hd + sum(tail)
case Nil => 0
}
不是这样的:
// wrong!
def sum(ls: List[Int]): Int = {
ls match {
case hd :: tail => hd + sum(tail)
case Nil => 0
}
}
4、多个参数列表
一般来说,如果有很好的理由,你应该只使用多个参数列表。这些方法(或类似声明的函数)具有更详细的声明和调用语法,对于经验不足的Scala开发人员来说更难理解。
你应该这样做的主要原因有三个:
流畅的API
多个参数列表允许您创建自己的“控制结构”:
def unless(exp: Boolean)(code: => Unit): Unit = if (!exp) code
unless(x < 5) {
println("x was not less than five")
}
隐含参数
当使用隐式参数,并使用
implicit
关键字时,它适用于整个参数列表。因此,如果只希望某些参数是隐式的,则必须使用多个参数列表。对于类型推断
当仅使用某些参数列表调用方法时,在调用其余参数列表时,类型参考者可以允许更简单的语法。考虑折叠
def foldLeft[B](z: B)(op: (A,B) => B): B
List("").foldLeft(0)(_ + _.length)
// If, instead:
def foldLeft[B](z: B, op: (B, A) => B): B
// above won't work, you must specify types
List("").foldLeft(0, (b: Int, a: String) => a + b.length)
List("").foldLeft[Int](0, _ + _.length)
对于复杂的DSL,或类型名称长,可能难以将整个签名放在一行上。在这些情况下,将参数列表的开放式对齐,每行一个列表(即,如果您不能将它们全部放在一行,每行一个):
protected def forResource(resourceInfo: Any)
(f: (JsonNode) => Any)
(implicit urlCreator: URLCreator, configurer: OAuthConfiguration): Any = {
...
}
5、高阶函数
当声明高阶函数时,Scala允许在函数参数作为最后一个参数被调用时,在调用位置为这些函数提供更好的语法。例如,这是foldl
SML中的功能:
fun foldl (f: ('b * 'a) -> 'b) (init: 'b) (ls: 'a list) = ...
在Scala中,首选风格是正确的:
def foldLeft[A, B](ls: List[A])(init: B)(f: (B, A) => B): B = ...
通过最后放置函数参数,我们启用了如下所示的调用语法:
foldLeft(List(1, 2, 3, 4))(0)(_ + _)
此调用中的函数值不包括在括号中; 它在语法上与函数本身(foldLeft
)截然不同。这种风格的优点是其简洁和清洁。
(三)、字段
字段应遵循方法的声明规则,特别注意访问修饰符排序和注释约定。
Lazy vals应该lazy
直接使用关键字val
:
private lazy val foo = bar()
二、功能值
Scala提供了许多用于声明函数值的不同语法选项。例如,以下声明完全相同:
val f1 = ((a: Int, b: Int) => a + b)
val f2 = (a: Int, b: Int) => a + b
val f3 = (_: Int) + (_: Int)
val f4: (Int, Int) => Int = (_ + _)
在这些风格中,(1)和(4)将始终是首选。(2)在这个例子中看起来更短,但是每当功能值跨越多行(通常情况下)时,这种语法就变得非常笨重。同样,(3)简明扼要。未经训练的眼睛很难破译这甚至产生功能值的事实。
当专门使用样式(1)和(4)时,很容易区分使用函数值的源代码中的位置。两种样式都使用括号,因为它们在单行上看起来很干净。
(一)、间距
括号和它们包含的代码之间不应有空格。
花括号应与其间的代码分开一个空格,给视觉上忙碌的大括号“呼吸室”。
(二)多表达式函数
大多数函数值不如上面给出的例子那么微不足道。许多包含多个表达式。在这种情况下,跨多个行分割函数值通常更易读。当这种情况发生时,只应使用风格(1),用括号代替括号。当包含大量代码时,样式(4)变得非常难以遵循。声明本身应该松散地遵循方法的声明样式,其中大括号与分配或调用在同一行,而闭括号在紧跟在函数的最后一行之后是自己的行。参数应与开启括号相同,如“arrow”(=>
):
val f1 = { (a: Int, b: Int) =>
val sum = a + b
sum
}
如前所述,函数值应尽可能地利用类型推断。