Style Guide
General Guidelines
Code formatters
We use scalafmt and sbt-java-formatter to format code automatically and keep the code style consistent, and sbt-header to generate copyright file header.
See scalafmt
configuration file.
Sbt
plugin
Run the following command to format the entire codebase.
sbt scalafmtAll javafmtAll headerCreateAll
IntelliJ IDEA
Most of us write Scala code in IntelliJ IDEA and it’s wise to let the IDE do most of the work including managing imports and formatting code. To use scalafmt
you need an additional plugin. Follow this link to install it.
We also want to avoid custom settings as much as possible to make on-boarding new developers easier. Hence, we use IntelliJ IDEA’s default settings with the following exceptions:
- Set Code Style → Scala → Formatter → scalafmt
Scalafix
Scalafix is a refactoring and linting tool that we rely on as well to keep our code tidy. Currently, we use the following rules:
rules = [
RemoveUnused,
LeakingImplicitClassVal
]
Run the following command to let scalafix refactor your code.
sbt scalafixEnable scalafixAll
References
We want to adhere to the styles of well known Scala projects and use the following documents as references when scalafmt
needs a little bit of help. We follow the Databricks Scala Guide mainly with a few differences described in the next section.
- Databricks Scala Guide
- The Official Scala Style Guide
- Twitter’s Effective Scala
Differences from Databricks Scala Guide
Spacing and Indentation
- For method declarations, align parameters when they don’t fit in a single line. Return types on the same line as the last parameter.
def saveAsBigQuery(
table: TableReference,
schema: TableSchema,
writeDisposition: WriteDisposition,
createDisposition: CreateDisposition,
tableDescription: String,
timePartitioning: TimePartitioning)(implicit ev: T <:< TableRow): ClosedTap[TableRow] = {
// method body
}
def saveAsTypedBigQuery(
tableSpec: String,
writeDisposition: WriteDisposition = TableWriteParam.DefaultWriteDisposition,
createDisposition: CreateDisposition = TableWriteParam.DefaultCreateDisposition,
timePartitioning: TimePartitioning = TableWriteParam.DefaultTimePartitioning)(
implicit tt: TypeTag[T],
ev: T <:< HasAnnotation,
coder: Coder[T]): ClosedTap[T] = {
// method body
}
- For classes whose header doesn’t fit in a single line and exceed the line, align the next line and add a blank line after class header.
class Foo(val param1: String,
val param2: String,
val param3: Array[Byte])
extends FooInterface // 2 space indent here
with Logging {
def firstMethod(): Unit = { /* body */ } // blank line above
}
Blank Lines (Vertical Whitespace)
- A single blank line appears:
- Between consecutive members (or initializers) of a class: fields, constructors, methods, nested classes, static initializers, instance initializers.
- Within method bodies, as needed to create logical groupings of statements.
- Before the first member and after the last member of the class.
- A blank line is optional:
- Between consecutive one-liner fields or methods of a class that have no ScalaDoc.
- Before the first member and after the last member of a short class with one-liner members only.
- Use one blank line to separate class definitions.
- Excessive number of blank lines is discouraged.
class Foo {
val x: Int // blank line before the first member
val y: Int
val z: Int // no blank line between one-liners that have no ScalaDoc
def hello(): {
// body
} // blank line after the last member
}
// no blank line before the first member and after the last member
class Bar {
def x = { /* body */ }
def y = { /* body */ }
}
Curly Braces
Put curly braces even around one-line conditional or loop statements. The only exception is if you are using if/else as an one-line ternary operator that is also side-effect free.
// the only exception for omitting braces
val x = if (true) expression1 else expression2
Documentation Style
Use Java docs style instead of Scala docs style. One-liner ScalaDoc is acceptable. Annotations like @tparam
, @param
, @return
are optional if they are obvious to the user.
ScalaDoc /** */
should only be used for documenting API to end users. Use regular comments e.g. /* */
and //
for explaining code to developers.
/** This is a correct one-liner, short description. */
/**
* This is correct multi-line JavaDoc comment. And
* this is my second line, and if I keep typing, this would be
* my third line.
*/
/** In Spark, we don't use the ScalaDoc style so this
* is not correct.
*/
// @param xs, @tparam T and @return are obvious and no need to document
/** Sum up a sequence with an Algebird Semigroup. */
def sum[T: Semigroup](xs: Seq[T]): T = xs.reduce(implicitly[Semigroup[T]].plus)
Ordering within a Class
If a class is long and has many methods, group them logically into different sections, and use comment headers to organize them.
class ScioContext {
// =======================================================================
// Read operations
// =======================================================================
// =======================================================================
// Accumulators
// =======================================================================
}
Of course, the situation in which a class grows this long is strongly discouraged, and is generally reserved only for building certain public APIs.
Imports
- Mutable collections
- Always prefix a mutable collection type with
M
when importing, e.g.import scala.collection.mutable.{Map => MMap}
- Or import
scala.collection.mutable
package and usemutable.Map
- Sort imports in IntelliJ IDEA’s default order:
java.*
- All other imports
scala.*
- It should look like this in Imports → Import Layout
java
_______ blank line _______
all other imports
_______ blank line _______
scala