风格指南

命名约定

语言

一般来说,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 设计过程中,符号方法名称应严格保留用于纯函数操作。因此,为两个单子定义 >>= 方法是可以接受的,但为输出流定义 << 方法是不可接受的。前者在数学上定义明确且没有副作用,而后者则不是。

一般来说,符号方法名称应该是众所周知且本质上是自文档化的。经验法则如下:如果您需要解释方法的作用,那么它应该有一个真实的描述性名称,而不是符号。在极少数情况下,可以接受发明新的符号方法名称。你的 API 很可能不是这种情况!

在 Scala 中,使用符号名称定义方法应被视为一项高级功能,只有那些最精通其缺陷的人才能使用。如果不加注意,过度使用符号方法名称很容易将最简单的代码转换成符号汤。

常量、值和变量

常量名称应采用大驼峰式。类似于 Java 的 static final 成员,如果成员是 final、不可变的,并且它属于包对象或对象,则可以将其视为常量

object Container {
  val MyConstant = ...
}

值: scala.math 包中的 Pi 是此类常量的另一个示例。

值和变量名称应采用小驼峰式

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 的一部分,因为用户可以在方法调用中使用具名参数。

此页面的贡献者