- Use tabs for indentation.
- Add a newline to the end of every file.
- There must not be whitespace characters at the end of a line
- Never put two blank lines in a row
- (Almost) always put a blank line between two declarations in a class
- Insert blank lines at will between logical blocks of statements in a method
The blank line between two consecutive declarations in a class can sometimes be omitted, if the declarations are single-line (which also means ScalaDocless) and strongly related. This happens pretty rarely (mostly a series of private fields). The rule of thumb is to always put a blank line.
There must not be any space before the following tokens: :
,
;
)
...
There must be exactly one space after the following tokens: :
,
;
if
for
while
...
Binary operators, including =>
, must have a single space on both sides.
Sometimes, spaces can be removed to highlight the relatively higher priority wrt. to a neighboring operator, for easier visual parsing.
For example, instead of x < len - 1
, it is better to write x < len-1
, highlighting that -
has a higher priority than <
.
Unary operators must not be followed by a space.
Do NOT use vertical alignment. They draw attention to the wrong parts of the code and make the aligned code harder to change in the future.
// Don't align vertically
val plus = "+"
val minus = "-"
val multiply = "*"
// Do the following
val plus = "+"
val minus = "-"
val multiply = "*"
- Use a maximum line length of 100 characters. In some cases, if breaking a line makes it significantly less readable, it can go up to 160 characters.
- The only exceptions are import statements and URLs (although even for those, try to keep them under 100 chars).
- If a line exceed length limit, it can be broken after either a
,
or a(
, or awith
statement or possibly after a binary operator in a long expression.
-
Classes, traits, objects and constants should follow Java class convention, i.e. PascalCase style.
class ClusterManager trait Expression
-
Methods/functions should be named in camelCase style.
-
Packages should follow Java package naming conventions, i.e. all-lowercase ASCII letters.package com.playtech.dataplatform
File names for package objects must match the most specific name in the package. For example, for the
com.playtech.dataplatform
package object, the file should be nameddataplatform.scala
and the file should be structured like this:package com.playtech package object dataplatform { //... }
-
Use camel case for acronyms, for example,
HttpUtil
. For consistency, this includes class names likeTtl
andJson
as well as common terms such asDb
andIo
. Follow normal camel-casing rules.val httpIoJsonToXmlParser = new HttpIoJsonToXmlParser()
-
Use short names for small scopes and longer names for larger scopes.
-
Use common abbreviations but eschew esoteric ones. Everyone knows
ok
,err
ordefn
whereassfri
is not so common. -
Don't rebind names for different uses.
-
Use active names for operations with side effects:
user.activate()
notuser.setActive()
. -
Use descriptive names for methods that return values:
src.isDefined
notsrc.defined
. -
Don't prefix getters with get:
site.count
notsite.getCount
. -
Don't repeat names that are already encapsulated in package or object name
// Correct:
object User {
def get(id: Int): Option[User]
}
// Wrong:
object User {
// User.getUser provides no more information than User.get.
def getUser(id: Int): Option[User]
}
-
Put imports at the top of the file.
-
Do not use relative imports from other packages.
// Wrong: import com.playtech import util // Correct: import com.playtech.util
-
Avoid using wildcard imports, unless you are importing more than 6 entities, or implicit methods. Wildcard imports make the code less robust to external changes.
-
Always import packages using absolute paths (e.g.
scala.util.Random
) instead of relative ones (e.g.util.Random
). -
In addition, sort imports in the following order:
java.*
andjavax.*
scala.*
akka.*
spray.*
- Third-party libraries (
org.*
,com.*
, etc) - Project classes
-
Within each group, imports should be sorted in alphabetic ordering.
-
You can use IntelliJ's import organizer to handle this automatically, using the following config, which you should add to your IntelliJ configuration (found in Preferences > Code Style > Scala):
java javax ___blank line___ scala ___blank line___ akka spray all other imports ___blank line___ com.playtech
-
Braces should not be used for one-liner methods.
def plus(x: Int, y: Int): Int = x + y
-
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.
// Correct: if (true) { println("Wow!") } // Correct: if (true) statement1 else statement2 // Correct: try { foo() } catch { ... } // Wrong: if (true) println("Wow!") // Wrong: try foo() catch { ... }
-
Every expression that spans more than one line should be enclosed in braces.
// Wrong: val iterator = new Iterator[A] { ... } // Correct: val iterator = { new Iterator[A] { ... } } // Wrong: def clamp(v: Int, min: Int, max: Int): Int = if (v < min) min else if (v > max) max else v // Correct: def clamp(v: Int, min: Int, max: Int): Int = { if (v < min) min else if (v > max) max else v }
-
As an exception to the above rule, when a two-liner
if/else
(see section aboutif/else
in general) is used as the right-hand-side of a definition or assignment, i.e., after an=
sign, the braces around it can and should be omitted. For example:def abs(x: Int): Int = if (x >= 0) x else -x
-
Note that the following formatting is not considered a two-liner if/else, and is therefore not valid:
def abs(x: Int): Int = if (x >= 0) x else -x
-
When you
match
on any type, follow these rules:- Pattern matching should be exhaustive and explicitly handle the failure/default case rather than relying on a runtime
MatchError
. (This is specific to match blocks and not case statements in partial functions.) Case classes used in pattern matching should extend a commonsealed trait
so that compiler warnings will be generated for inexhaustive matching. - Indent all
case
statements at the same level, and put the=>
one space to the right of the closing)
- Short single line expressions should be on the same line as the
case
- Long single line and multi-line expressions should be on the line below the case, indented one level from the
case
. - Do not add extra newlines in between each case statement.
- Filters on case statements should be on the same line if doing so will not make the line excessively long.
Here's a complete example:
Option(123) match { case Some(i: Int) if i > 0 => val intermediate = doWorkOn(i + 1) doMoreWorkOn(intermediate) case _ => 123 }
- Pattern matching should be exhaustive and explicitly handle the failure/default case rather than relying on a runtime
-
For method whose entire body is a pattern match expression, put the match on the same line as the method declaration if possible to reduce one level of indentation.
def test(msg: Message): Unit = msg match { case ... }
- Place the comment on a separate line, not at the end of a line of code.
- Begin comment text with an uppercase letter.
- End comment text with a period.
- Insert one space between the comment delimiter
//
and the comment text, as shown in the following example. Complete example:
// The following declaration creates a query. It does not run
// the query.
val query = ...
- Always prefer scaladocs to plain comments when you comment classes or methods.
-
Side-effect-free methods without formal parameters should be declared without (), unless either it overrides a method defined with () (such as toString()) or it implements a Java method in the Java libraries.
-
All public functions and methods, including those inside
object
s must have an explicit result return type. -
Procedure syntax must not be used.
: Unit =
must be used instead. -
If a function has a parameter list with fewer than 70 characters, put all parameters on the same line:
def add(a: Int, b: Int): Int = { ... }
-
If a function has several long parameter lists, format it in this way:
def lotsOfParams (param1: Int, param2: Int, param3: Int) (param4: Int, param5: Int, param6: Int): Int = { ... }
Or, if it has a list of implicit parameters:
def lotsOfParams( aReallyLongParameterNameOne: Int, aReallyLongParameterNameTwo: Int, aReallyLongParameterNameThree: Int, aReallyLongParameterNameFour: Int) (implicit adder: Adder, reader: Reader): Int = { ... }
-
In all cases, the function's return type still has to be written directly following the last closing parenthesis.
-
Symbolic methods must always be aliases of regular methods and be declared like that:
class X{
def add(other: X): X = {
...
}
// It's clear that + in an alias of add.
def +(other: X): X = this.add(other)
}
-
Usually, parentheses should be used for actual parameters to a method call. Braces should be used instead if an argument list has only a lambda, and that lambda does not fit in an inline one-liner.
-
In general, dot-notation should be used for non-symbolic methods, and infix notation should be used for symbolic methods. Infix notation is also used if the only argument is a brace lambda.
Examples:
// inline lambda, hence (), hence dot-notation list.map(x => x * 2) // long lambda, hence braces, hence infix notation list map { x => if (x < 5) x else x * 2 } // symbolic operator, hence infix notation value :: list
-
Callsite should follow method declaration, i.e. if a method is declared with parentheses, call with parentheses. Note that this is not just syntactic. It can affect correctness when
apply
is defined in the return object.class Foo { def apply(args: String*): Int } class Bar { def foo: Foo } new Bar().foo // This returns a Foo new Bar().foo() // This returns an Int!
-
If you pass a function which takes a single argument, then argument and underscore should be omitted. For example:
// Correct: Option(123).map(println) // Wrong: Option(123).map(println(_))
-
Use pattern matching directly in function calls whenever applicable.
// Wrong: list map { item => item match { case Some(x) => x case None => default } } // Correct: list map { case Some(x) => x case None => default }
-
When calling a function with a closure (or partial function), if there is only one case, put the case on the same line as the function invocation.
list.zipWithIndex.map { case (elem, i) => // ... }
If there are multiple cases, indent and wrap them.
list.map { case a: Foo => ... case b: Bar => ... }
-
When calling functions with numerous arguments, place the first parameter on the next line after function and align the remaining parameters with the first:
fooBar( someVeryLongFieldName, andAnotherVeryLongFieldName, "this is a string", 3.1415)
-
It's your choice whether to place closing parenthesis directly following the last parameter or on a new line (in "dangling" style).
You can do this:
aLongMethodNameThatReturnsAFuture( aParam, anotherParam, aThirdParam ).map { res => ... }
Or this:
aLongMethodNameThatReturnsAFuture( aParam, anotherParam, aThirdParam).map { res => ... }
-
Anonymous functions start on the same line as preceding code. Declarations start with
{
(note the space before and after the{
). Arguments are then listed on the same line. A few more notes:- Do not use braces after the argument list, just start the function body on the next line.
- Argument types are not necessary. You should use them if it makes the code clearer though.
Here's a complete example:
Option(123).map { number => println(s"the number plus one is: ${number + 1}") }
-
In lambdas (anonymous functions), the opening brace must be placed before the formal arguments, and not after the
=>
:val f = { (x: Int) => body }
-
If the first line ends up being two long, the parameter list should go the next line, and the body indented one level more:
val someLongIdentifierWithHighIdentation = { (x: Int, ys: List[Traversable[String]]) => body }
-
Use parentheses and an underscore for anonymous functions that are:
- single binary operations
- a single method invocation on the input
- two or fewer unary methods on the input
Examples:
val list = List("list", "of", "strings") list.filter(_.length > 2) list.filter(_.contains("i"))
Suffix long literal values with uppercase L
. It is often hard to differentiate lowercase l
from 1
.
val longValue = 5432L // Do this
val longValue = 5432l // Do NOT do this