Skip to content

Instantly share code, notes, and snippets.

@Vitaliy-Savkin
Last active March 30, 2016 14:04
Show Gist options
  • Save Vitaliy-Savkin/4bf3f7ff77a395737b45 to your computer and use it in GitHub Desktop.
Save Vitaliy-Savkin/4bf3f7ff77a395737b45 to your computer and use it in GitHub Desktop.

Syntactic Style

  • 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 a with 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 named dataplatform.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 like Ttl and Json as well as common terms such as Db and Io. 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 or defn whereas sfri is not so common.

  • Don't rebind names for different uses.

  • Use active names for operations with side effects: user.activate() not user.setActive().

  • Use descriptive names for methods that return values: src.isDefined not src.defined.

  • Don't prefix getters with get: site.count not site.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.* and javax.*
    • 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 about if/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:

    1. 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 common sealed trait so that compiler warnings will be generated for inexhaustive matching.
    2. Indent all case statements at the same level, and put the => one space to the right of the closing )
    3. Short single line expressions should be on the same line as the case
    4. Long single line and multi-line expressions should be on the line below the case, indented one level from the case.
    5. Do not add extra newlines in between each case statement.
    6. 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
    }
  • 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 objects 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment