類別
類別、物件和特質建構函式應全部宣告在同一行,除非該行「太長」(約 100 個字元)。在這種情況下,請將每個建構函式引數放在自己的行中,並加上 尾隨逗號
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 = …
}
如果類別/物件/特質延伸任何內容,套用相同的通用規則,將其放在一行上,除非超過約 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 {
def firstMethod: Foo = …
}
類別元素的順序
所有類別/物件/特質成員都應交錯宣告,並以換行符號分隔。此規則的唯一例外是 var
和 val
。這些可以不使用中間換行符號宣告,但前提是沒有任何欄位有 Scaladoc,且所有欄位都有簡單(最多約 20 個字元,一行)的定義
class Foo {
val bar = 42
val baz = "Daniel"
def doSomething(): Unit = { ... }
def add(x: Int, y: Int): Int = x + y
}
欄位應在範圍中先於方法。唯一的例外是,如果 val
有區塊定義(多於一個表達式),並執行可能被視為「類似方法」的運算(例如,計算 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
程序語法
避免使用(現已棄用的)程序語法,因為它往往會造成混淆,而且簡潔性並沒有顯著提升。
// don't do this
def printBar(bar: Baz) {
println(bar)
}
// write this instead
def printBar(bar: Bar): Unit = {
println(bar)
}
修飾詞
方法修飾詞應按以下順序給出(如果每個都適用)
- 註解,每個都在自己的行上
- 覆寫修飾詞 (
override
) - 存取修飾詞 (
protected
,private
) - 隱含修飾詞 (
implicit
) - 最終修飾詞 (
final
) def
@Transaction
@throws(classOf[IOException])
override protected final def foo(): Unit = {
...
}
主體
當方法主體包含一個小於 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
}
}
多重參數清單
一般來說,只有有充分理由時才應使用多重參數清單。這些函式(或類似宣告的函數)具有較冗長的宣告和呼叫語法,且較難讓經驗較少的 Scala 開發人員理解。
有三個主要原因應這麼做
-
針對流暢的 API
多重參數清單允許您建立自己的「控制結構」
def unless(exp: Boolean)(code: => Unit): Unit = if (!exp) code unless(x < 5) { println("x was not less than five") }
-
隱含參數
使用隱含參數時,若您使用
implicit
關鍵字,則會套用至整個參數清單。因此,如果您只想讓部分參數為隱含,則必須使用多重參數清單。 -
針對類型推論
僅使用部分參數清單呼叫函式時,類型推論器可以在呼叫其餘參數清單時允許較簡單的語法。請考慮 fold
def foldLeft[B](z: B)(op: (B, A) => 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) => b + a.length) List("").foldLeft[Int](0, _ + _.length)
對於複雜的 DSL,或類型名稱很長,很難將整個簽章放在一行中。對於這些情況,有幾種不同的樣式可以使用
-
分割參數清單,每行一個參數,並使用 尾隨逗號 和括號,並在不同的行中以增加清單之間的視覺區隔
protected def forResource( resourceInfo: Any, )( f: (JsonNode) => Any, )(implicit urlCreator: URLCreator, configurer: OAuthConfiguration, ): Any = { ... }
-
或對齊參數清單的開啟括號,每行一個清單
protected def forResource(resourceInfo: Any) (f: (JsonNode) => Any) (implicit urlCreator: URLCreator, configurer: OAuthConfiguration): Any = { ... }
高階函式
宣告高階函式時,值得記住 Scala 允許在呼叫位置針對此類函式使用較好的語法,前提是函式參數作為最後一個引數進行 currying。例如,這是 SML 中的 foldl
函式
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 應在 val
之前直接使用 lazy
關鍵字
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) 會變得極難遵循。宣告本身應大致遵循方法的宣告樣式,其中開括號與指定或呼叫位於同一行,而閉括號則位於函式最後一行的下一行。參數應與開括號位於同一行,而「箭頭」(=>
) 也應如此
val f1 = { (a: Int, b: Int) =>
val sum = a + b
sum
}
如前所述,函式值應盡可能利用類型推論。