This page discusses the coding style and other guidelines for sbt 1.0.
sbt 1.0 will primarily target Scala 2.12. We will cross-build against Scala 2.10.
Before 1.0 is release, we should clean up deprecations.
On Scala 2.12 we should aim for zero warnings. One exception may be deprecation if it’s required for cross-building.
It is often useful to start with the Scaladoc before fleshing out a trait/class implementation by forcing you to consider the need for its existence.
All newly introduced public traits and classes and, to a lesser extent, functions and methods, should have Scaladoc. A significant amount of existing sbt code lacks documentation and we need to repair this situation over time. If you see an opportunity to add some documentation, or improve existing documentation then this will also help.
Package level documentation is a great place to describe how various components interact, so please consider adding/enhancing that where possible.
For more information on good Scaladoc style, please refer to the Scaladoc Style Guide
The fewer methods we can expose to the build user, the easier sbt becomes to maintain.
Code against interfaces.
The implementation details should be hidden behind sbt.internal.x
packages,
where x
could be the name of the main package (like io
).
Independent modules with fewer dependent libraries are easier to reuse.
Avoid exposing external classes in the API, except for standard Scala and Java classes.
A module may be declared internal if it has no use to the public.
-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
The -Xfatal-warnings
may be removed if there are unavoidable warnings.
Use the package name appended with the layer name, such as sbt.io
for IO layer.
The organization name for published artifacts should remain org.scala-sbt
.
A good overview on the topic of binary resiliency is Josh's 2012 talk on Binary resiliency. The guideline here applies mostly to publicly exposed APIs.
Use MiMa.
def
declarations only val
or var
in a trait
results in code generated at subclass and in the artificial Foo$class.$init$
lazy val
results in code generated at subclass
To trait, or not to trait?. Abstract classes are less flexible than traits, but traits pose more problems for binary compatibility. Abstract classes also have better Java interoperability.
If there’s no need to keep a class open, seal it.
If there’s no need to keep a class open, finalize it.
The typeclass pattern with pure traits might ease maintaining binary compatibility more so than subclassing.
Case classes involve code generation that makes it harder to maintain binary compatibility over time.
Default parameter values are effectively code generation, which makes them difficult to maintain.
Here are other guidelines about the sbt public API.
Define datatypes.
def apply
def apply
should be reserved for factory methods
in a companion object that returns type T
.
Vector
, List
, or Array
), rather than Seq
scala.Seq
is scala.collection.Seq
, which is not immutable.
Default to Vector
. Use List
if constant prepending is needed.
Use Array
if Java interoperability is needed.
Note that using mutable collections is perfectly fine within the implementation.
toSeq
or anything with side-effects on Set
Set
is fine if you stick to set operations, like contains
and subsetOf
.
More often than not, toSeq
is called explicitly or implicitly,
or some side-effecting method is called from map
.
This introduces non-determinism to the code.
toSeq
on Map
Same as above. This will introduce non-determinism.
Instead of functions and tuples, turn them into a trait. This applies where interoperability is a concern, like implementing incremental compilation.
sbt-houserules comes with scalafmt for formatting source code consistently.
Declare an explicit Unit
return.
This style is encouraged:
final class FooID {}
object FooID {
implicit val fooIdPicklerUnpicker: PicklerUnpickler[FooID] = ???
}
Avoid defining implicit converters in companion objects and package objects.
Suppose the IO module introduces a URL
enrichment called RichURI
,
and LibraryManagement introduces a String
enrichment called GroupID
(for ModuleID
syntax).
These implicit conversions should be defined in an object named syntax
in the respective package:
package sbt.io
object syntax {
implicit def uriToRichURI(uri: URI): RichURI = new RichURI(uri)
}
When all the layers are available, the sbt
package should also define an object called syntax
which forwards implicit conversions from all the layers:
package sbt
object syntax {
implicit def uriToRichURI(uri: URI): io.RichURI = io.syntax.uriToRichURI(uri)
....
}