1. sbt 编码规范

sbt 编码规范 

本页面讨论 sbt 1.0 的编码风格和其他指南。

总体目标 

sbt 1.0 将主要针对 Scala 2.12。我们将跨构建到 Scala 2.10。

清理旧的弃用 

在 1.0 发布之前,我们应该清理弃用。

目标为零警告(弃用除外) 

在 Scala 2.12 上,我们应该以零警告为目标。如果跨构建需要弃用,则可能是一个例外。

文档 

在详细说明特征/类实现之前,从 Scaladoc 开始通常很有用,因为它会迫使你考虑其存在的必要性。

所有新引入的 **公共** 特征和类,以及在较小程度上,函数和方法,都应该有 Scaladoc。大量现有的 sbt 代码缺乏文档,我们需要随着时间的推移修复这种情况。如果你看到有机会添加一些文档,或改进现有文档,那么这也有帮助。

包级文档是描述各种组件如何交互的绝佳地方,因此请考虑在可能的情况下添加/增强这些文档。

有关良好 Scaladoc 风格的更多信息,请参阅 Scaladoc 风格指南

模块化设计 

目标小 

我们可以公开给构建用户的 方法越少,sbt 就越容易维护。

公共 API 应该针对“接口”编码 

针对接口编码。

隐藏实现细节 

实现细节应该隐藏在 sbt.internal.x 包后面,其中 x 可以是主包的名称(如 io)。

减少相互依赖 

具有较少依赖库的独立模块更容易重用。

隐藏外部类 

避免在 API 中公开外部类,除了标准的 Scala 和 Java 类。

隐藏内部模块 

如果模块对公众没有用,则可以将其声明为内部模块。

编译器标志 

-encoding utf8
-deprecation
-feature
-unchecked
-Xlint
-language:higherKinds
-language:implicitConversions
-Xfuture
-Yinline-warnings
-Yno-adapted-args
-Ywarn-dead-code
-Ywarn-numeric-widen
-Ywarn-value-discard
-Xfatal-warnings

如果存在不可避免的警告,则可以删除 -Xfatal-warnings

包名和组织名称 

使用包名附加层名称,例如 IO 层的 sbt.io。发布工件的组织名称应保留为 org.scala-sbt

二进制弹性 

有关二进制弹性的一个很好的概述是 Josh 的 2012 年演讲关于二进制弹性。此处的指南主要适用于公开的 API。

MiMa 

使用 MiMa.

公共特征应该只包含 声明 

  • trait 中的 valvar 会在子类和人工 Foo$class.$init$ 中生成代码
  • lazy val 会在子类中生成代码

抽象类也很有用 

使用特征,还是不使用特征?。抽象类不如特征灵活,但特征会给二进制兼容性带来更多问题。抽象类还具有更好的 Java 互操作性。

密封特征和抽象类 

如果没有必要保持类开放,请将其密封。

完成叶子类 

如果没有必要保持类开放,请将其完成。

类型类和子类继承 

使用纯特征的类型类模式可能比子类化更容易维护二进制兼容性。

避免使用 case 类,使用 sbt-datatype 

case 类涉及代码生成,这使得随着时间的推移更难维护二进制兼容性。

优先使用方法重载而不是默认参数值 

默认参数值实际上是代码生成,这使得它们难以维护。

其他公共 API 事项 

以下是一些关于 sbt 公共 API 的其他指南。

避免使用字符串类型编程 

定义数据类型。

避免过度使用  

def apply 应该保留用于伴生对象中的工厂方法,该方法返回类型 T

使用特定数据类型(VectorListArray),而不是 Seq 

scala.Seqscala.collection.Seq,它不是不可变的。默认使用 Vector。如果需要恒定前置,请使用 List。如果需要 Java 互操作性,请使用 Array。请注意,在实现中使用可变集合是完全可以的。

避免对 或带有副作用的任何方法调用  

如果你坚持使用集合操作(如 containssubsetOf),那么 Set 就可以了。通常,toSeq 会显式或隐式地调用,或者从 map 中调用一些带有副作用的方法。这会给代码引入不确定性。

避免对 调用  

与上面一样。这会引入不确定性。

如果需要 Java 互操作性,请避免在签名中使用函数和元组 

不要使用函数和元组,而是将它们转换为特征。这适用于互操作性成为问题的地方,例如实现增量编译。

风格很重要 

使用 scalafmt 

sbt-houserules 附带 scalafmt 用于一致地格式化源代码。

避免使用过程语法 

声明显式的 Unit 返回值。

尽可能在伴生对象中定义类型类的实例 

鼓励使用这种风格

final class FooID {}
object FooID {
  implicit val fooIdPicklerUnpicker: PicklerUnpickler[FooID] = ???
}

用于语法的隐式转换(enrich-my-library 模式)应该被导入 

避免在伴生对象和包对象中定义隐式转换器。

假设 IO 模块引入了名为 RichURIURL 扩展,而 LibraryManagement 引入了名为 GroupID(用于 ModuleID 语法)的 String 扩展。这些隐式转换应该在各自包中名为 syntax 的对象中定义

package sbt.io

object syntax {
  implicit def uriToRichURI(uri: URI): RichURI = new RichURI(uri)
}

当所有层都可用时,sbt 包也应该定义一个名为 syntax 的对象,它转发来自所有层的隐式转换

package sbt

object syntax {
  implicit def uriToRichURI(uri: URI): io.RichURI = io.syntax.uriToRichURI(uri)
  ....
}