Introduction
Scala is one of the main application programming languages used at Twitter. Much of our infrastructure is written in Scala and we have several large libraries supporting our use. While highly effective, Scala is also a large language, and our experiences have taught us to practice great care in its application. What are its pitfalls? Which features do we embrace, which do we eschew? When do we employ “purely functional style”, and when do we avoid it? In other words: what have we found to be an effective use of the language? This guide attempts to distill our experience into short essays, providing a set of best practices. Our use of Scala is mainly for creating high volume services that form distributed systems — and our advice is thus biased — but most of the advice herein should translate naturally to other domains. This is not the law, but deviation should be well justified.
Scala provides many tools that enable succinct expression. Less typing is less reading, and less reading is often faster reading, and thus brevity enhances clarity. However brevity is a blunt tool that can also deliver the opposite effect: After correctness, think always of the reader.
Above all, program in Scala. You are not writing Java, nor Haskell, nor Python; a Scala program is unlike one written in any of these. In order to use the language effectively, you must phrase your problems in its terms. There’s no use coercing a Java program into Scala, for it will be inferior in most ways to its original.
This is not an introduction to Scala; we assume the reader is familiar with the language. Some resources for learning Scala are:
- Scala School
- Learning Scala
- Learning Scala in Small Bites
This is a living document that will change to reflect our current “best practices,” but its core ideas are unlikely to change: Always favor readability; write generic code but not at the expensive of clarity; take advantage of simple language features that afford great power but avoid the esoteric ones (especially in the type system). Above all, be always aware of the trade offs you make. A sophisticated language requires a complex implementation, and complexity begets complexity: of reasoning, of semantics, of interaction between features, and of the understanding of your collaborators. Thus complexity is the tax of sophistication — you must always ensure that its utility exceeds its cost.
And have fun.
Formatting
The specifics of code formatting — so long as they are practical — are of little consequence. By definition style cannot be inherently good or bad and almost everybody differs in personal preference. However the consistent application of the same formatting rules will almost always enhance readability. A reader already familiar with a particular style does not have to grasp yet another set of local conventions, or decipher yet another corner of the language grammar.
This is of particular importance to Scala, as its grammar has a high degree of overlap. One telling example is method invocation: Methods can be invoked with “.
”, with whitespace, without parenthesis for nullary or unary methods, with parenthesis for these, and so on. Furthermore, the different styles of method invocations expose different ambiguities in its grammar! Surely the consistent application of a carefully chosen set of formatting rules will resolve a great deal of ambiguity for both man and machine.
We adhere to the Scala style guide plus the following rules.
Whitespace
Indent by two spaces. Try to avoid lines greater than 100 columns in length. Use one blank line between method, class, and object definitions.
Naming
- Use short names for small scopes
i
s,j
s andk
s are all but expected in loops.- Use longer names for larger scopes
- External APIs should have longer and explanatory names that confer meaning.
Future.collect
notFuture.all
. - 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
val
s - Avoid using
`
s to overload reserved names. typ
instead of`type
`- 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
- As per the previous rule, it’s redundant:
site.count
notsite.getCount
- Don’t repeat names that are already encapsulated in package or object name
- Prefer:
object User { def get(id: Int): Option[User] }
to
object User { def getUser(id: Int): Option[User] }
They are redundant in use:
User.getUser
provides no more information thanUser.get
.
Imports
- Sort import lines alphabetically
- This makes it easy to examine visually, and is simple to automate.
- Use braces when importing several names from a package
import com.twitter.concurrent.{Broker, Offer}
- Use wildcards when more than six names are imported
- e.g.:
import com.twitter.concurrent._
Don’t apply this blindly: some packages export too many names - When using collections, qualify names by importing
scala.collection.immutable
and/orscala.collection.mutable
- Mutable and immutable collections have dual names. Qualifiying the names makes is obvious to the reader which variant is being used (e.g. “
immutable.Map
“) - Do not use relative imports from other packages
- Avoid
import com.twitter import concurrent
in favor of the unambiguous
import com.twitter.concurrent
- Put imports at the top of the file
- The reader can refer to all imports in one place.