My Personal Pragmatic Scala Style Guide

ยท 807 words ยท 4 minute read

This is my personal style guide on how to write Scala programs that scale in terms of developers. It’s a distillation that contains the essence of observations I’ve had over many years, working with teams of different sizes and backgrounds, from Scala pros to complete newbies.

All these guidelines are given with the ultimate goal of making development fast and fun, for yourself, and for you in six months and other people working with you.

In general follow the principle of least power ๐Ÿ”—

Li Haoyi has a great article on this.

In general, the principles he describes can be broadly summarized as:

  • Write code that is “easy to read” and “less complicated”
  • Don’t fear refactoring
  • Don’t over-engineer in anticipation for future needs that may never appear.

I highly recommend you read his article.

Write code as if it will be read as plain text ๐Ÿ”—

Code is usually read more than it is written. In particular, it is often read as plain text, not from within an IDE, let alone a fully setup toolchain. This means that a source file should be mostly self-explanatory, without the need of understanding the encompassing project, and without the need for fancy IDE utilities such as type inference or jump-to-definition.

  • No wildcard imports. Avoid these like the plague. Not only do they obscure where definitions come from, but they open you up to conflicts in case the imported namespace changes in the future.

    There is one exception to this rule. If you’re truly buying into a whole DSL, a wildcard import may be simpler than multiple explicit ones. But note that this is basically akin to adopting a new language in your system, so you should really think hard before doing that. Especially make sure that the DSL you’ve now enabled in a file is well documented.

  • Limit your number of imports. Use import aliases, scoped imports, and partially-qualified paths to reduce the number of file-level imports you need to keep in you head when reading code later on.

    The number of dots you need to reference an identifier should be proportional to how often it is used in a file. For example:

    • if outer.inner.foo.Bar is used often, then it’s fine to import it directly with outer.inner.foo.Bar and refer to it simply as Bar

      import outer.inner.foo.Bar
      // many lines later
      val bar: Bar = ...
      
    • if outer.inner.foo.Bar is used once, then it’s overall simpler to refer to it explicitly

      val bar: outer.inner.foo.Bar = ...
      
    • if you use more than one class from a package e.g. outer.inner.foo.Bar and outer.inner.foo.Baz, and you don’t use them too frequently (but still more than once), then I recommend to import the package and refer to them in a partially-qualified manner

      import outer.inner.foo
      // many lines later
      val x = foo.Bar
      val y = foo.Baz
      
  • Annotate return types and variable types. In top-level declarations, as well as in local definitions in case the type is non-obvious.

Prefer immutability but don’t shun mutability ๐Ÿ”—

Scala deliberately allows you to write code with side-effects. It is sometimes easier and/or more efficient to program imperatively with mutation and early returns.

However, don’t forget that the whole point of programming languages is to help humans reason about and grow their programs. This means that you should attempt to keep your mutable and side-effecting code local.

Prefer synchronous code ๐Ÿ”—

By synchronous code, I mean code that uses only native syntax constructs and doesn’t wrap itself inside and around futures or other concurrency constructs. This is also called “direct-style” sometimes.

The reasons are that you most likely won’t be bottlenecked by threads anyway (especially with green threads making it back to the JVM under project loom), and that wrapping everything inside futures does make your code more difficult to read.

Please note that this guideline should apply only as a default and is only about the code style itself. If you have reasons to use futures then by all means use them. I’m not saying that the future API is bad, just that you shouldn’t use it in your code if you don’t have to.

Dependencies ๐Ÿ”—

Know that every dependency you take is a commitment. By adding a dependency, your code now depends on someone else’s work which you do not control and whose evolution you cannot influence.

  • Chose dependencies judiciously
  • Prefer copying short snippets of code than depending on a binary
  • Prefer dependencies with few dependencies themselves
  • Prefer depending on projects which are mature, and to which you can upstream changes

The JVM has no easy way to namespace different versions of a jar, hence dependencies can break each other (fixing this requires “shading”, complicated mess). Hence, be conservative.


In a nutshell, you should write boring, readable code. You should focus on solving your problem at hand, in the most straight-forward way, using the language features at your disposal but not obsessing over them.

comments powered by Disqus