scala编码规范风格

本文翻译自: https://docs.scala-lang.org/style/

1.缩进

采用2个空格,而不是tab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// wrong!
class Foo {
def fourspaces = {
val x = 4
..
}
}

// right!
class Foo {
def twospaces = {
val x = 2
..
}
}

换行

换行一般是80个字符。

每个连续的行应该从第一行缩进两个空格。 还要记住,Scala要求每个“换行”要么具有未闭合的括号,要么以中缀方法结束,其中没有给出正确的参数:

1
2
3
val result = 1 + 2 + 3 + 4 + 5 + 6 +
7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 +
15 + 16 + 17 + 18 + 19 + 20

如果没有这种尾随方式,Scala会在一行的末尾推断出一个分号,它有时会包装,有时甚至不会发出警告而抛弃编译。

有许多参数的方法

当调用一个有大量参数(5个或更多)的方法时,通常需要将方法调用包装到多行上。 在这种情况下,将每个参数单独放在一行上,从当前缩进级别缩进两个空格:

1
2
3
4
5
foo(
someVeryLongFieldName,
andAnotherVeryLongFieldName,
"this is a string",
3.1415)

当太长时,将方法名也换到下一行:

1
2
3
4
5
6
7
8
9
10
11
12
13
// right!
val myLongFieldNameWithNoRealPoint =
foo(
someVeryLongFieldName,
andAnotherVeryLongFieldName,
"this is a string",
3.1415)

// wrong!
val myLongFieldNameWithNoRealPoint = foo(someVeryLongFieldName,
andAnotherVeryLongFieldName,
"this is a string",
3.1415)

命名约定

一般来说,Scala使用驼峰命名法。

1
2
UpperCamelCase
lowerCamelCase

首字母缩略词应视为普通词:

1
2
3
4
5
6
7
使用:
Xhtml
maxId

而不是:
XHTML
maxID

编译器实际上不禁止名称(_)中的下划线,但强烈建议不要这样,因为它们在Scala语法中具有特殊含义。

Classes/Traits

使用驼峰:

1
class MyFairLady

Objects

对象名称类似于类名(上层驼峰案例)。

模仿包或函数时例外。 这并不常见。 例:

1
2
3
4
5
6
7
8
9
10
object ast {
sealed trait Expr

case class Plus(e1: Expr, e2: Expr) extends Expr
...
}

object inc {
def apply(x: Int): Int = x + 1
}

Package

Scala包应遵循Java包命名约定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// wrong!
package coolness

// right! puts only coolness._ in scope
package com.novell.coolness

// right! puts both novell._ and coolness._ in scope
package com.novell
package coolness

// right, for package object com.novell.coolness
package com.novell
/**
* Provides classes related to coolness
*/
package object coolness {
}

root

有时需要使用_root_完全限定导入。例如,如果另一个net包在范围内,那么要访问net.liftweb我们必须这样写:

1
import _root_.net.liftweb._

不要过度使用_root_。通常,嵌套包解析是一件好事,对减少导入混乱很有帮助。使用_root_不仅会否定它们的好处,而且还会引入额外的混乱。

方法

方法的名称应该使用小写开头的驼峰:

1
def myFairMethod = ...

get/set方法

scala没有和java一样的get/set方法,但有下面的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Foo {

def bar = ...

def bar_=(bar: Bar) {
...
}

def isBaz = ...
}

val foo = new Foo
foo.bar // accessor
foo.bar = bar2 // mutator
foo.isBaz // boolean property

私有变量:

1
2
3
4
5
6
7
8
9
class Company {
private var _name: String = _

def name = _name

def name_=(name: String) {
_name = name
}
}

括号

方法声明可以有、也可以没有括号:

1
2
3
def foo1() = ...

def foo2 = ...

但是,建议调用时和声明时一样。

1
2
3
4
5
6
7
8
// doesn't change state, call as birthdate
def birthdate = firstName

// updates our internal state, call as age()
def age() = {
_age = updateAge(birthdate)
_age
}

符号方法名

尽量避免,虽然可以用。例如:a+b(), c::d, >>=.

常量、值、变量、方法

类似java的static final, scala里常量的声明采用大写首字母的驼峰命名:

1
2
3
object Container {
val MyConstant = ...
}

而其他命名如下:

1
2
3
val myValue = ...
def myMethod = ...
var myVariable

泛型

不像Java的泛型用T,scala使用从A开始的字母:

1
2
3
class List[A] {
def map[B](f: A => B): List[B] = ...
}

或者:

1
2
3
4
5
6
7
8
9
10
11
// Right
class Map[Key, Value] {
def get(key: Key): Value
def put(key: Key, value: Value): Unit
}

// Wrong; don't use all-caps
class Map[KEY, VALUE] {
def get(key: KEY): VALUE
def put(key: KEY, value: VALUE): Unit
}
1
2
3
4
class Map[K, V] {
def get(key: K): V
def put(key: K, value: V): Unit
}

高级参数类型

1
2
3
class HigherOrderMap[Key[_], Value[_]] { ... }

def doSomething[M[_]: Monad](m: M[Int]) = ...

注解

应该是小写:

1
2
3
4
5
class cloneable extends StaticAnnotation

type id = javax.persistence.Id @annotation.target.field
@id
var id: Int = 0

简短的特殊性

在java里可能不是个好习惯,但scala里推荐:

1
def add(a: Int, b: Int) = a + b

类型

引用类型

// TODO

注解

1
value: Type

冒号写在value后面而不是Type前面是有原因的,如下:

1
value :::

可能会有Type是::的。

Ascription

// TODO

函数类型

函数作为参数时,能省括号就省,一元参数时不加括号:

1
2
3
def foo(f: Int => String) = ...

def bar(f: (Boolean, Double) => List[String]) = ...

极端例子:

1
2
3
4
5
// wrong!
def foo(f: (Int) => (String) => (Boolean) => Double) = ...

// right!
def foo(f: Int => String => Boolean => Double) = ...

结构类型

低于50个字符就写在一行,否则分开:

1
2
3
4
5
6
7
8
9
10
// wrong!
def foo(a: { def bar(a: Int, b: Int): String; val baz: List[String => String] }) = ...

// right!
private type FooParam = {
val baz: List[String => String]
def bar(a: Int, b: Int): String
}

def foo(a: FooParam) = ...

内联:

1
def foo(a: { val bar: String }) = ...

大括号

左大括号必须和声明的语句在同一行:

1
2
3
def foo = {
...
}

小括号

很长的语句可以换行:

1
2
(this + is a very ++ long *
expression)

条件语句:

1
2
3
4
(  someCondition
|| someOtherCondition
|| thirdCondition
)

声明

class

class/object/trait的构造方法还是看情况,能一行就一行,否则分开:

1
2
3
4
5
6
7
8
9
10
11
12
class Person(name: String, age: Int) {
}

class Person(
name: String,
age: Int,
birthdate: Date,
astrologicalSign: String,
shoeSize: Int,
favoriteColor: java.awt.Color) {
def firstMethod: Foo = ...
}

继承也是一样:

1
2
3
4
5
6
7
8
9
10
11
12
class Person(
name: String,
age: Int,
birthdate: Date,
astrologicalSign: String,
shoeSize: Int,
favoriteColor: java.awt.Color)
extends Entity
with Logging
with Identifiable
with Serializable {
}

类元素的顺序

换行:

1
2
3
4
5
6
7
8
class Foo {
val bar = 42
val baz = "Daniel"

def doSomething(): Unit = { ... }

def add(x: Int, y: Int): Int = x + y
}

方法

如下声明方式:一定加上返回类型,否则私有方法或局部方法会忽略。

1
2
3
def foo(bar: Baz): Bin = expr

def foo(x: Int = 6, y: Int = 7): Int = x + y

如下:

1
2
3
4
5
6
7
8
9
// don't do this
def printBar(bar: Baz) {
println(bar)
}

// write this instead
def printBar(bar: Bar): Unit = {
println(bar)
}

修饰符

声明顺序:

  1. 注解
  2. Override
  3. 访问修饰符(private ,protected
  4. implicit
  5. final
  6. def
1
2
3
4
5
@Transaction
@throws(classOf[IOException])
override protected final def foo(): Unit = {
...
}

函数体

当函数体长度小于30个字符,写在一行:

1
def add(a: Int, b: Int): Int = a + b

在30到70个字符时:

1
2
def sum(ls: List[String]): Int =
ls.map(_.toInt).foldLeft(0)(_ + _)

但推荐更易读的方式:

1
2
3
4
def sum(ls: List[String]): Int = {
val ints = ls map (_.toInt)
ints.foldLeft(0)(_ + _)
}

对于match语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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
}
}