風格指南

命名慣例

語言

一般來說,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 + bc :: d

在前一種情況下,只要語法實際上有益,就可以不受懲罰地使用符號方法名稱。然而,在標準 API 設計過程中,符號方法名稱應嚴格保留給純函數運算。因此,定義一個 >>= 方法來加入兩個 monad 是可以接受的,但定義一個 << 方法來寫入輸出串流則不可接受。前者在數學上定義良好且沒有副作用,而後者則兩者皆非。

一般來說,符號方法名稱應易於理解且具有自我說明的性質。經驗法則如下:如果你需要解釋方法的作用,那麼它應該有一個真實的描述性名稱,而不是符號。在極少數情況下,可以接受發明新的符號方法名稱。你的 API 很可能不是這些情況之一!

在 Scala 中,使用符號名稱定義方法應被視為進階功能,僅供精通其陷阱的人使用。如果不注意,過度使用符號方法名稱很容易將最簡單的程式碼轉換成符號湯。

常數、值和變數

常數名稱應使用大寫駝峰式。類似於 Java 的 static final 成員,如果成員是最終的、不可變的,並且屬於套件物件或物件,則可以視為常數

object Container {
  val MyConstant = ...
}

值:Piscala.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 的一部分,因為使用者可以在方法呼叫中使用具名稱的參數。

此頁面的貢獻者