一般來說,Scala 使用「駝峰式大小寫」命名法。也就是說,每個字都大寫,除了第一個字可能例外
UpperCamelCase
lowerCamelCase
縮寫詞應視為一般字詞
xHtml
maxId
而不是
XHTML
maxID
名稱中的底線 (_
) 雖然編譯器並未禁止,但強烈建議不要使用,因為它們在 Scala 語法中具有特殊意義。(但請參閱下方的例外情況。)
類別/特質
類別應以大駝峰式大小寫命名
class MyFairLady
這模仿了 Java 類別的命名慣例。
有時特質和類別以及它們的成員用於描述格式、文件或協定,並產生/衍生它們。在這些情況下,最好與輸出格式有 1:1 的關係,且不套用命名慣例。在這種情況下,它們應僅用於特定目的,而不要用於其餘程式碼。
物件
物件名稱類似類別名稱(大寫駝峰式大小寫)。
模仿套件或函式時例外。這並不常見。範例
object ast {
sealed trait Expr
case class Plus(e1: Expr, e2: Expr) extends Expr
...
}
object inc {
def apply(x: Int): Int = x + 1
}
套件
Scala 套件應遵循 Java 套件命名慣例
// 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_
完全限定匯入。例如,如果另一個 net
在範圍內,則要存取 net.liftweb
,我們必須寫入例如
import _root_.net.liftweb._
不要過度使用 _root_
。一般來說,巢狀套件解析很好,且有助於減少匯入混亂。使用 _root_
不僅會抵消其好處,還會引入額外的混亂。
方法
方法的文字(字母)名稱應為小寫駝峰式大小寫
def myFairMethod = ...
本節並非 Scala 慣用語法方法命名的完整指南。可以在方法呼叫部分找到更多資訊。
存取器/變異器
Scala 不會遵循 Java 慣例,在變異器和存取器方法中分別加上 set
/get
。相反地,會使用下列慣例
- 對於屬性的存取器,方法名稱應該是屬性名稱。
- 在某些情況下,可以在布林存取器中加上前綴 “`is`”(例如
isEmpty
)。這只應在沒有提供對應變異器時使用。請注意,Lift 慣例在布林存取器中加上後綴 “_?
” 並非標準,且不會在 Lift 框架之外使用。 -
對於變異器,方法名稱應該是屬性名稱,加上後綴 “
_=
”。只要在封閉類型中定義了具有該特定屬性名稱的對應存取器,此慣例就會啟用呼叫點變異語法,該語法反映了指定。請注意,這不僅是慣例,也是語言的要求。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
不幸的是,這些慣例違反了 Java 慣例,即根據存取器和變異器封裝的私有欄位命名,以表示它們所代表的屬性。例如
public class Company {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
在 Scala 中,欄位和方法之間沒有區別。事實上,欄位完全由編譯器命名和控制。如果我們想在 Scala 中採用 Java 的 bean getter/setter 慣例,這是一個相當簡單的編碼
class Company {
private var _name: String = _
def name = _name
def name_=(name: String) {
_name = name
}
}
雖然匈牙利表示法非常醜陋,但它確實具有消除 _name
變數歧義的優點,而不會使識別符號混亂。底線位於前綴位置,而不是後綴位置,以避免錯誤地輸入 name _
而不是 name_
。大量使用 Scala 的類型推論,此類錯誤可能會導致非常混淆的錯誤。
請注意,Java 的 getter/setter 範例通常用於解決缺乏對屬性和繫結的一流支援。在 Scala 中,有支援屬性和繫結的函式庫。慣例是使用對包含其自己的 getter 和 setter 的屬性類別的不可變參考。例如
class Company {
val string: Property[String] = Property("Initial Value")
括號
與 Ruby 不同,Scala 重視方法是否以括號宣告(僅適用於元數-0 的方法)。例如
def foo1() = ...
def foo2 = ...
這些是在編譯時不同的方法。雖然 foo1
可以使用或不使用括號呼叫,但 foo2
不能使用括號呼叫。
因此,在宣告方法時是否使用括號非常重要。
作為任何類型存取器的函式(封裝欄位或邏輯屬性)應不使用括號宣告,除非它們有副作用。雖然 Ruby 和 Lift 使用 !
來表示這一點,但建議使用括號(請注意,流暢的 API 和內部特定領域語言傾向於為了語法而打破以下準則。此類例外不應被視為違反,而應視為這些規則不適用的時候。在 DSL 中,語法應優先於慣例)。
此外,呼叫位置應遵循宣告;如果宣告時帶有括弧,呼叫時也應帶有括弧。雖然省下幾個字元很誘人,但如果你遵循此準則,你的程式碼將會更易於閱讀和維護。
// doesn't change state, call as birthdate
def birthdate = firstName
// updates our internal state, call as age()
def age() = {
_age = updateAge(birthdate)
_age
}
符號方法名稱
避免!儘管 Scala 在這個 API 設計領域提供了很大的便利,但定義具有符號名稱的方法不應掉以輕心,特別是當符號本身是非標準時(例如,>>#>>
)。一般來說,符號方法名稱有兩個有效的用例
- 特定領域語言(例如,
actor1 ! Msg
) - 邏輯數學運算(例如,
a + b
或c :: d
)
在前一種情況下,只要語法實際上有益,就可以不受懲罰地使用符號方法名稱。然而,在標準 API 設計過程中,符號方法名稱應嚴格保留給純函數運算。因此,定義一個 >>=
方法來加入兩個 monad 是可以接受的,但定義一個 <<
方法來寫入輸出串流則不可接受。前者在數學上定義良好且沒有副作用,而後者則兩者皆非。
一般來說,符號方法名稱應易於理解且具有自我說明的性質。經驗法則如下:如果你需要解釋方法的作用,那麼它應該有一個真實的描述性名稱,而不是符號。在極少數情況下,可以接受發明新的符號方法名稱。你的 API 很可能不是這些情況之一!
在 Scala 中,使用符號名稱定義方法應被視為進階功能,僅供精通其陷阱的人使用。如果不注意,過度使用符號方法名稱很容易將最簡單的程式碼轉換成符號湯。
常數、值和變數
常數名稱應使用大寫駝峰式。類似於 Java 的 static final
成員,如果成員是最終的、不可變的,並且屬於套件物件或物件,則可以視為常數
object Container {
val MyConstant = ...
}
值:Pi
在 scala.math
套件中是此類常數的另一個範例。
值和變數名稱應使用小寫駝峰式
val myValue = ...
var myVariable
類型參數(泛型)
對於簡單的類型參數,應使用單一的大寫字母(來自英文字母),從 A
開始(這與從 T
開始的 Java 慣例不同)。例如
class List[A] {
def map[B](f: A => B): List[B] = ...
}
如果類型參數有更具體的意義,應使用描述性名稱,遵循類別命名慣例(而不是全大寫樣式)
// 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
}
如果類型參數的範圍夠小,可以使用助記符號代替較長、描述性的名稱
class Map[K, V] {
def get(key: K): V
def put(key: K, value: V): Unit
}
高階和參數化類型參數
理論上,高階與一般類型參數沒有不同(除了其 種類 至少為 *=>*
,而不是單純的 *
)。命名慣例通常類似,但為了清楚起見,建議使用描述性名稱,而不是單一字母
class HigherOrderMap[Key[_], Value[_]] { ... }
單一字母形式(有時)可接受用於整個程式碼庫中使用的基本概念,例如函子的 F[_]
和單子的 M[_]
。
在這種情況下,基本概念應該是團隊眾所周知且理解的,或有第三方的證據,例如以下
def doSomething[M[_]: Monad](m: M[Int]) = ...
在此,類型綁定 : Monad
提供必要的證據來告知讀者 M[_]
是 Monad 的類型。
註解
註解,例如 @volatile
應為小寫駝峰式大小寫
class cloneable extends StaticAnnotation
此慣例用於整個 Scala 函式庫,即使它與 Java 註解命名不一致。
注意:此慣例即使在註解上使用類型別名時也適用。例如,在使用 JDBC 時
type id = javax.persistence.Id @annotation.target.field
@id
var id: Int = 0
關於簡潔的特別說明
由於 Scala 根植於函數式語言,因此本地名稱非常簡短是很正常的
def add(a: Int, b: Int) = a + b
這在 Java 等語言中會是不良做法,但在 Scala 中卻是好做法。此慣例之所以可行,是因為寫得好的 Scala 方法很簡短,只跨越一個表達式,而且很少超過幾行。很少使用本地名稱(包括參數),因此無需設計出冗長且具描述性的名稱。此慣例大幅提升了大多數 Scala 來源的簡潔性。這反過來又提高了可讀性,因為大多數表達式都適合一行,而方法的參數具有描述性類型名稱。
此慣例僅適用於非常簡單方法的參數(以及非常簡單類別的本地欄位);公開介面中的所有內容都應具有描述性。另請注意,參數名稱現在是類別公開 API 的一部分,因為使用者可以在方法呼叫中使用具名稱的參數。