The Scail Scala Style Guide contains recommendations for formatting conventions, coding style, and best practices to be used in all Scail projects. It is based on vast experience gathered across numerous Scala projects.
The guide is organized as follows:
- The first section, "Code Formatting", covers only lexical
(layout) code conventions, i.e., guidelines that do not alter the semantic
meaning of a program:
- The section itself highlights the main points of the official Scala Style Guide. Reading the official Scala guide itself is required.
- The subsection "Additions and Deviations from the Official Style Guide" contains the points where we differ from the official guide plus a few items not covered by it. To ease on-boarding and favor consistency, we try to deviate as little as possible from the official guide.
- The second section, "Best Practices" covers rules that may
change the semantic meaning of the code:
- "Additional recommendations" are, for the most part, required rules that must be followed unless there is a very good reason not to. Failure to obey these conventions may introduce errors, degrade performance, or create maintenance headaches.
- "Tips & Tricks" are mostly friendly reminders that may not apply in all situations. Always keep them in mind and use your best judgment.
- "Additional Remarks" contains general hints that are always helpful to remember.
All Scail projects make extensive use of Static Analysis Tools to enforce as many best practices as is currently allowed. Use the provided EditorConfig profile to configure your editor. Also see Recommended IntelliJ Settings.
Please read the Scala Style Guide carefully. The main points to consider are:
-
Use two-space indentation. No tabs.
-
Omit unnecessary blocks to reduce excessive nesting:
if (condition) { bad() } if (condition) good()
-
Avoid wrapping lines. Split long lines into multiple expressions, assigning intermediate results to
val
s. -
Use lower camel case for
valName
,varName
,methodName
,functionObject
,packageObject
, andannotationName
. -
Use upper camel case for
ConstantName
,EnumerationValue
,ClassName
,TraitName
,ObjectName
,TypeParameter
. -
No
UPPERCASE_UNDERSCORE
, not even for constants or type parameters. -
No
get
/set
prefixes for accessors and mutators. -
Always use empty parentheses when, and only when, declaring or calling methods with side effects. That includes using factory methods like
Seq.empty
instead ofSeq()
. -
Unless an established convention already exists (e.g, mathematical symbols), avoid symbolic method names ("operators").
-
Use type inference where possible. But put clarity first and favor explicitness when an inferred type may not be obvious.
val good = 1 // Int, obviously val bad = config.get("key") // What does it return? val better: Option[String] = config.get("key")
-
Opening curly braces (
{
) must be on the same line as the declaration. -
Constructors should be declared all on one line. If not possible, put each constructor argument on its own line.
-
Class extensions follow the same rule: either all on one line or each on its own line.
-
Favor short, single-expression, single-line method bodies.
-
No procedure syntax.
def bad() { ??? } def worse { ??? } def good(): Unit = { ??? }
-
Postfix operator notation is unsafe and shall not be used. Consider it deprecated. Never import
scala.language.postfixOps
.val bad = seq mkString val good = seq.mkString
-
Always use infix notation for methods with symbolic names or higher-order functions (
map
,foreach
, etc.).val bad = seq.map(_ * 2) val good = seq map (_ * 2)
When the syntax does not allow it, stick with traditional method invocation syntax:
val bad = (seq map f).toSet // Starts to read like LISP val good = seq.map(f).toSet
-
Text file format: UTF-8, no BOM, Unix line ending (LF, '\n'), newline at EOF.
-
100 characters maximum line length.
-
No trailing whitespace at the end of lines, they cause problems when diffing between files or between versions. Configure your text editor to do this automatically for you.
-
One blank line between method, class, and object definitions.
-
Use blank lines between statements and declarations to create logical groupings.
-
No double blank lines, anywhere.
-
No double (four-space) indent, including class constructors, method declarations, and multi-line statements (wrapped long lines).
val bad = fourSpace && doubleIndent val good = multiLineExpression + twoSpace + singleIndent class Platypus( name: String , age: Int ) extends Beaver with Duck
-
Acronyms follow camel case rules:
Http
,Json
,Xml
,Db
,Io
. -
In general, obey English rules and mathematical conventions for punctuation:
- Single spaces around
=>
,=
,<-
,{
,-
,+
,}
,)
,*
, etc. - A single space after and no space before
:
,,
,)
,;
, etc. - A single space before
(
, except for method invocation or declaration. - No space after
(
and[
. - No space before
[
and]
. - No spaces between consecutive braces, brackets, or parentheses.
- Single spaces around
-
Use comma-first style.
-
Do not align vertically: it demands constant realignment, making diffs longer and code reviews more difficult.
-
Methods must always have explicit return types. Explicitly declaring the return type allows the compiler to verify correctness.
-
Modifiers should be declared in the following order:
override
,abstract
,private
orprotected
,final
,sealed
,implicit
,lazy
. -
Put imports at the top of the file. Imports should be grouped from most to least specific:
- The project's own classes
- Frameworks and libraries:
com._
,net._
,org._
,play._
, etc. scala._
java._
andjavax._
(Mnemonic: your project is built on top of frameworks and libraries, which are built on top of Scala, which itself is built on top of Java.)
Inside each group, packages and classes must be sorted alphabetically. Separate groups with blank lines:
import myapp.util.StringUtils import org.joda.time.DateTime import play.api.Configuration import scala.concurrent.Future import java.net.URI
-
Do not use relative imports. Full imports are easier to search, and never ambiguous:
package foo.bar // Bad import baz.Qux // Good import foo.bar.baz.Qux
-
One import per line, as in Java.
-
Do not wildcard-import entire packages:
import bad._
-
The bigger the scope, the more descriptive the name. Only for very small, local scopes may single-letter mnemonics be used.
-
Use
_
for simple, single line functions:val bad = seq filter (number => number % 2 == 0) val good = seq filter (_ % 2 == 0)
-
Omit the
_
for functions that take a single argument:bad foreach println(_) good foreach println
-
Use infix notation for single argument methods on monadic types (
contains
,getOrElse
, etc.) -
For single-line functions and for-comprehensions, use parentheses. For multi-line ones, use brackets:
for (i <- 1 to 3) println(i) seq map (_ * 2) seq map { a => if (a < 0) -a else a }
-
Whenever possible, simplify pattern matching expressions by omitting the
match
keyword and using partial functions:bad map { x => x match { case 1 => "one" case _ => "not one" } } good map { case 1 => "one" case _ => "not one" }
-
When passing functions, do not use inner block syntax:
(bad => { ??? }) { good => ??? }
-
For documentation comments, use Javadoc left-hand margin convention instead of Scaladoc style:
/** Bad * convention */ /** * Good */
-
Optimize for readability. Readability trumps consistency (but not by much). Consistency trumps everything else.
-
Do not abuse "suppress warning" annotations.
Although in rare, exceptional situations there may be legitimate uses, mindlessly suppressing warnings defeats the purpose of static analysis. Take a warning as an opportunity to rethink your design and refactor as needed for improvements. Only after careful consideration contemplate the need to suppress it.
-
For unit tests, use DiagrammedAssertions. Do not use, import, or mix in matchers.
-
Avoid pattern matching Options. Use the Scala Option Cheat Sheet as reference.
-
Never use
return
: http://tpolecat.github.io/2014/05/09/return.html -
Parentheses in Scala are not optional. Be aware of extraneous parentheses:
val bad = Set.empty() // expands to Set.empty.apply(), evaluates to false val evil = Seq(1,2,3).toSet() // same as above
-
Use the most generic collection type possible, typically one of:
Iterable[T]
,Seq[T]
,Set[T]
, orMap[T]
. (Program to an interface, not an implementation.) -
Use
Seq[T]
, notList[T]
(http://stackoverflow.com/a/10866807/410286) except where you specifically need to force one implementation over another. The most common exception is that Play form mappers requireList[T]
, so you have to use it there.Seq
is the interface whileList
the implementation, analogous toMap
andHashMap
in Java. -
When possible, use
scala.collection.breakOut
to avoid producing and converting intermediate collections with.toMap
,.toSeq
,.toSet
, etc.val cities = Seq("Toronto", "New York", "San Francisco") // Produces an intermediate Seq[(String, Int)], converts to Map[String, Int] val bad = cities.map(s => (s, s.length)).toMap // No intermediate values or conversions involved val good: Map[String, Int] = cities.map(s => (s, s.length))(breakOut)
Please note that the type annotation is required or
breakOut
will not be able to infer and use the proper builder. To know whenbreakOut
can be used, check in the Scaladoc if the higher-order function (map
in the example above) takes an implicitCanBuildFrom
parameter. Notice thatbreakOut
is being passed in lieu of the implicit parameter. -
Do not overuse tuples, decompose them or, better, use case classes:
val paul = Some(("Paul", 42)) // Bad paul map (p => s"Name: ${p._1}, Age: ${p._2}") // Good paul map { case (name, age) => s"Name: $name, Age: $age" } // Better case class Person(name: String, age: Int) val paul = Some(Person("Paul", 42)) paul map (p => s"Name: ${p.name}, Age: ${p.age}")
-
Do not overdo method chaining, use
val
s to name intermediate steps:val bad = xs.filter(...).map(...).exists(...) val filteredXs = xs filter (...) val ys = filteredXs map xToY val answer = ys exists (...)
-
Avoid defining new implicit conversions (
implicit def
). Instead, use the safer and more efficient implicit value classes (extension methods):class Bad(n: Int) { def stars = "*" * n } implicit def bad(n: Int) = new Bad(n) implicit class Good(val n: Int) extends AnyVal { def stars = "*" * n }
-
Whenever possible, avoid matching on
case _
. Try to be specific and thorough, favor exhaustive matches. Do not let unforeseen conditions silently fall through. -
Never use
null
, useOption
instead. If dealing with legacy APIs, wrap possiblenull
s:Option(javaClass.returnsNull()) javaClass.takesNull(option.orNull)
-
Avoid
Some.apply
, useOption.apply
instead.Option.apply
protects againstnull
, whileSome.apply
is perfectly happy to returnSome(null)
, eventually raising an unexpected null pointer exception. Also note thatSome.apply
is type-inferred asSome[T]
, notOption[T]
.val bad = Some(System.getenv("a")) // NPE waiting to happen val good = Option(System.getenv("a")) // Would return None
By the principle of least astonishment, use
Option.apply
even if you "know" your reference can never be null. By being consistent, we avoid wondering whether aSome.apply
was overlooked by the developer or not:val bad = Some(math.random()) val good = Option(math.random()) // And never worry about it again
-
Do not abuse
Option
. Some types already provide a good default to represent "nothing". For instance, before declaring anOption[Seq[T]]
, ask yourself whether there is any semantic difference betweenSome(Nil)
andNone
. If not (and usually, there isn't), useSeq[T]
and return an empty list. -
Never extend a case class. Extending a case class with another case class is forbidden by the compiler. Extending a case class with a regular class, while permitted, produces nasty results:
case class A(a: Int) // error: case class B has case ancestor A, // but case-to-case inheritance is prohibited. case class B(a: Int, val b: String) extends A(a) class C(a: Int, c: Int) extends A(a) val a = A(1) val c = new C(1, 2) assert(a == c) assert(a.hashCode == c.hashCode) assert(c.toString == "A(1)") // Wat val d = new C(1, 3) assert(c.hashCode == d.hashCode) val e = c.copy(4) // note there is no C#copy(Int, Int) method assert(!e.isInstanceOf[C]) // e is a proper instance of A
-
Do not use
JavaConversions
. Instead useJavaConverters
and itsasScala
andasJava
methods.JavaConversions
may happen "automagically" at unexpected times, usually masquerading type errors.JavaConverters
gives you explicit control of when conversions happen (only as dangerous as you want). You can then transparently use Java collections as if they were Scala collections, usually for performance or interoperability reasons:import scala.collection.JavaConverters.mapAsScalaMapConverter import scala.collection.mutable val map: mutable.Map[String, String] = new java.util.HashMap[String, String].asScala map += "foo" -> "bar" assert(map("foo") == "bar")
// Counter-example val m = Map(1 -> "one") m.contains("") // Fails to compile: type mismatch import collection.JavaConversions._ m.contains("") // false
-
Instead of throwing exceptions, use a more suitable type:
Option
,Either
, orTry
.Exceptions are not functional. Functions that throw exceptions are not total functions, they do not return a value for all possible inputs.
-
Do not
try
tocatch
exceptions,Try
to catch exceptions.Try
does for exceptions whatOption
does fornull
. -
Remember that
Future
already encapsulates aTry
. Instead ofFuture[Try[T]]
, useFuture.failed
,Future.fromTry
, andFuture.successful
. -
No matter what, never use a "catch-all" exception handler. Some features in Scala are implemented relying on exceptions. Use
NonFatal
instead:import scala.util.control.NonFatal try { 1 / 0 } catch { case e: Exception => // Bad case _: Throwable => // Worse case _ => // Worst, scalac will warn and "recommend" the expression above case NonFatal(e) => // The only acceptable way to catch all exceptions }
Please note that
NonFatal
is not needed when pattern matchingFuture
orTry
since they already filter for it. -
Make judicious use of the various assertions (contracts) offered by Scala. See scala.Predef for the complete reference.
-
Assertions (
assert(b != 0)
) are used to document and check design-by-contract invariants in code. They can be disabled at runtime with the-Xdisable-assertions
command line option. -
require
is used to check pre-conditions, blaming the caller of a method for violating them. Unlike other assertions,require
throwsIllegalArgumentException
instead ofAssertionError
and can never be disabled at runtime.Make ample and liberal use of
require
, specially in constructors. Do not allow invalid state to ever be created:case class Person(name: String, age: Int) { require(name.trim.nonEmpty, "name cannot be empty") require(age >= 0, "age cannot be negative") require(age <= 130, "oldest unambiguously documented person to ever live died at age 122") }
Please note that even though
require
throws an exception, this is not a violation of the previous recommendations. Constructors are not methods, they cannot returnTry[Person]
. -
ensuring
is used on a method return value to check post-conditions:def square(a: Int) = {a * a} ensuring (_ > 0)
-
-
Know and make good use of the standard Scala collections library:
- Never test for
c.length == 0
(may be O(n)), usec.isEmpty
instead. - Instead of
c.filter(_ > 0).headOption
, usec find(_ > 0)
. - Instead of
c.find(_ > 0).isDefined
, usec exists (_ > 0)
. - Instead of
c exists (_ == 0)
, usec contains 0
.
This is not only a matter of style: for some collections, the recommendations above can be algorithmically more efficient than the naive approach.
- Never test for
-
Do not import
scala.collection.mutable._
or any single mutable collection directly. Instead, import themutable
package itself and use it explicitly as a namespace prefix to denote mutability, which also avoids name conflicts if using both mutable and immutable structures in the same scope:import scala.collection.mutable.Set val bad = Set(1, 2, 3) // Too subtle and risky for the inattentive reader import scala.collection.mutable val good = mutable.Set(1, 2, 3)
-
Prefer a mutable
val
over an immutablevar
:import scala.collection.mutable var bad = Set(1, 2, 3) bad += 4 val good = mutable.Set(1, 2, 3) good += 4 assert(good.sameElements(bad))
-
No "stringly" typed code. Use
Enumeration
orsealed
types withcase
objects.Enumeration
andsealed
types have similar purpose and usage, but they do not fully overlap.Enumeration
, for instance, does not check for exhaustive matching whilesealed
types do not, well, enumerate.object Season extends Enumeration { type Season = Value val Spring, Summer, Autumn, Winter = Value } sealed trait Gender case object Male extends Gender case object Female extends Gender
-
When creating new
Enumeration
s, always define a type alias (as above). Never useMyEnum.Value
to refer to the type of enumeration values:def bad(a: Season.Value) = ??? import Season.Season def good(a: Season) = ???
-
If a function takes multiple arguments of the same type, use named parameters to ensure values are not passed in the wrong order:
def geo(latitude: Double, longitude: Double) = ??? val bad = geo(a, b) val good = geo(latitude = a, longitude = b)
-
Always use named parameters with booleans, even when they are the only parameter:
// Bad Utils.delete(true) // Good Utils.delete(recursively = true)
-
Avoid declaring functions with boolean arguments ("magic booleans"). Do not model any two arbitrary states as boolean:
// Bad case class Person(male: Boolean) case class Car(isManual: Boolean, isElectric: Boolean, isTwoDoor: Boolean) // Good case class Person(gender: Gender) // Bad def findPeople(filterMales: Boolean): Seq[People] // Good def findMales: Seq[People] def findFemales: Seq[People]
-
Do not define abstract
val
s in traits or abstract classes. Abstractval
s are a source of headaches and unexpected behavior in Scala:trait Bad { val bad: Int val worse = bad + bad } object ImBad extends Bad { val bad = 1 } assert(ImBad.worse == 2) assert(ImBad.worse == 0)
Always define abstract
def
s, they are more general and safer:trait Good { def good: Int val better = good + good } object ImGood extends Good { def good = 1 } assert(ImGood.better == 2)
Note: beware that overriding the abstract
def
with a concreteval
also suffers from the problem above. -
Avoid
lazy val
.lazy val
is not free, or even cheap. Use it only when you absolutely need laziness semantics for correctness, never for "optimization". The initialization of alazy val
is expensive due to monitor acquisition cost, while every access is expensive due tovolatile
. Worse,lazy val
may deadlock even when there are no circular dependencies between them.lazy val
s are compiled into a double-checked locking instance with a dedicatedvolatile
guard. Notably, this compilation template yields a source of significant performance woes: we need to pass through thevolatile
read on the guard boolean for every read of thelazy val
. This creates write barriers, cache invalidation and general mayhem with a lot of the optimizations that HotSpot tries to perform. All for a value that may or may not even care about asynchronous access, and even more importantly, is guaranteed to be true only once and is false indefinitely thereafter (baring hilarious tricks with reflection). Contrary to what is commonly believed, this guard isn't simply optimized away by HotSpot (evidence for this comes from benchmarks on warm code and assembly dumps).Daniel Spiewak
-
With default parameters, secondary constructors are a lot less frequently needed in Scala than in Java But they can still be quite useful, just avoid pathological cases:
class Bad(a: Int, b: Int) { def this(a: Int) = this(a, 0) } class Good(a: Int, b: Int = 0)
-
If you need to wrap a value that can be immediately computed into a
Future
, useFuture.successful
, which returns an already completedFuture
. CallingFuture.apply
incurs all the overhead of starting an asynchronous computation, no matter if the computation is a simple, immediate result:val bad = Future(0) val good = Future.successful(str.trim)
-
When combining
Future
s in for-comprehensions, do not use the following idiom:for { a <- futureA b <- futureB c <- futureC } yield a + b + c
Unless
futureB
depends ona
andfutureC
depends onb
, that will unnecessarily chain (serialize) theFuture
s, starting a future only after the previous one has finished, which most likely defeats the purpose. To properly startFuture
s in parallel and combine their results, use the following idiom:val fa = futureA val fb = futureB val fc = futureC for { a <- fa b <- fb c <- fc } yield a + b + c
-
Avoid
isInstanceOf
orasInstanceOf
. Safe casting is actually one of the best use cases for pattern matching. It is more flexible, guarantees that a cast will only happen if it can succeed, and allows, for instance, the use of different branches to carry out multiple conditional casts at the same time for various types, perform conversions, or fallback to a default value instead of throwing an exception:val bad = if (a.isInstanceOf[Int]) x else y val good = a match { case _: Int => x case _ => y } val bad = a.asInstanceOf[Int] val good = a match { case i: Int => i case d: Double => d.toInt case _ => 0 // Or just throw new ClassCastException }
-
Do not use structural types, never import
scala.language.reflectiveCalls
. Structural types are implemented with reflection at runtime, and are inherently less performant than nominal types.
-
Avoid naked base types, such as
String
,Int
, orDate
. Those unwrapped types have no semantic meaning and provide little type safety (e.g., mixingfirstName
andlastName
). Instead, make plentiful use of value classes to enforce strong, semantic typing:// No extra object allocation at runtime case class Email(email: String) extends AnyVal // Never gets mixed with other strings val email = Email("[email protected]")
-
Combine value classes with implicit classes to define extension methods:
implicit class IntOps(val n: Int) extends AnyVal { def stars = "*" * n } // Equivalent to a static method call, no conversion actually takes place 5.stars
-
Whenever possible, use
private[this]
andfinal val
instead ofprivate
andval
as they enable the Scala compiler and the JVM to perform additional optimizations, namely, direct field access instead of accessor method, and inlined constant instead of field access. (Iffinal val
surprised you, remember that it is not redundant: in Scalafinal
means "cannot be overridden", while in Java it means both that as well as "cannot be reassigned".) -
Leverage parallel collections, use
.par
judiciously. -
import System.{currentTimeMillis => now}
orimport System.{nanoTime => now}
are very useful to have around. -
Use the
@tailrec
annotation to ensure the compiler can recognize a recursive method is tail-recursive. -
You can use the
@scala.beans.BeanProperty
and@BooleanBeanProperty
annotations to automatically generate JavaBeans style getter and setter methods. -
You can use the following syntax to pass a sequence as a parameter to a variable length argument list (varargs) method:
def foo(args: Int*) = ??? val seq = Seq(1, 2, 3) foo(seq: _*)
(Mnemonic: it looks like an emoticon)
-
There are some really neat tricks we can do with pattern matching:
val tuple = ("foo", 1, false) val (x, y, z) = tuple assert((x, y, z) == (("foo", 1, false))) case class Foo(x: String, y: Int) val foo = Foo("foo", 1) val Foo(a, b) = foo assert((a, b) == (("foo", 1))) val seq = Seq(1, 2, 3, 4, 5, 6) val x :: xs = seq assert((x, xs) == ((1, Seq(2, 3, 4, 5, 6)))) // Same as above val Seq(y, ys@_*) = seq assert((y, ys) == ((1, Seq(2, 3, 4, 5, 6)))) // Skipping elements val _ :: a :: b :: _ :: zs = seq assert((a, b, zs) == ((2, 3, Seq(5, 6)))) // Works with other collections, too val vector = Vector(1, 2, 3, 4, 5, 6) val Vector(_, a, b, _, ws@_*) = vector assert((a, b, ws) == ((2, 3, Vector(5, 6)))) val Array(key, value) = "key:value".split(':') // Regular expressions val regex = """(.)(.)(.)""".r val regex(a, b, c) = "xyz" // Matches and extracts regex against "xyz" assert((a, b, c) == (("x", "y", "z")))
Please note that the extra parentheses are needed due to the
-Yno-adapted-args
compiler option. -
Java
enum
s are very powerful and flexible. Enumeration types in Java can include methods and fields, and enum constants are able to specialize their behavior. For more information, refer to the awesome "Effective Java, 2nd Edition" by Joshua Bloch, in particular Chapter 6, "Enums and Annotations", Item 30: "Use enums instead of int constants".Unfortunately, Scala enums are not at feature parity with their Java counterparts. They do not, by default, offer the same flexibility and power:
- They do not interoperate with Java.
- They have the same type after erasure (see below).
- Pattern matching is not exhaustively checked.
- Play JSON and Scala Pickling do not support enumerations.
Do not expect the situation to change.
object A extends Enumeration { val Abc = Value } object X extends Enumeration { val Xyz = Value } object DoesNotCompile { def f(v: A.Value) = "I'm A" def f(v: X.Value) = "I'm X" }
The following idioms bring a little more power to Scala enums:
// Custom properties object Suit extends Enumeration { import scala.language.implicitConversions type Suit = SuitVal implicit def toVal(v: Value) = v.asInstanceOf[SuitVal] case class SuitVal private[Suit] (symbol: Char) extends Val val Spades = SuitVal('♠') val Hearts = SuitVal('♥') val Diamonds = SuitVal('♦') val Clubs = SuitVal('♣') } // Behaviour specialization and exhaustiveness checking object Lang extends Enumeration { import scala.language.implicitConversions type Lang = LangVal implicit def toVal(v: Value) = v.asInstanceOf[LangVal] sealed abstract class LangVal extends Val { def greet(name: String): String } case object English extends LangVal { def greet(name: String) = s"Welcome, $name." } case object French extends LangVal { def greet(name: String) = s"Bienvenue, $name." } }
-
Instead of running sbt tasks directly from the command line (
$ sbt compile
, for instance), it is better to open an sbt prompt (just type$ sbt
) and never leave it. Running all your sbt tasks (clean
,update
,compile
,test
, etc.) inside the sbt prompt is a lot faster since you only have to start sbt, load the JVM, and wait for it to warm up (if ever) once. If yourbuild.sbt
file changes, just run thereload
task and you are good to go again.Having an sbt instance running
~test
in the background is one of the best ways to develop in Scala. You can run some sbt tasks and be left inside the prompt by using theshell
task:$ sbt clean update compile test:compile shell
. -
The following shorthand trick works in the Scala REPL:
scala> "The quick brown fox jumps over the lazy dog!" res0: String = The quick brown fox jumps over the lazy dog! scala> .toLowerCase res1: String = the quick brown fox jumps over the lazy dog! scala> .distinct res2: String = the quickbrownfxjmpsvlazydg! scala> .filter(_.isLetter) res3: String = thequickbrownfxjmpsvlazydg scala> .sorted res4: String = abcdefghijklmnopqrstuvwxyz scala> .size res5: Int = 26
-
Learn you a few Scala tricks for great good: http://github.com/marconilanna/talks. Recommended talks:
- Powerful and Elegant Scala One-Liners
- What's New since Programming in Scala
- Idiomatic Scala: Your Options Do Not Match
- F-Bounded Polymorphism
-
Program in Scala. You are not writing Java, Haskell, or Python.
-
Leverage type safety. Let the compiler and the type system do the grunt work for you. Type early, type often.
"When in doubt, create a type"
Martin Fowler
-
Favor immutability, avoid mutability whenever possible. Mutability encapsulated in small scopes internal to functions is acceptable.
-
Obey the principle of least astonishment.
-
Always favor readability. Be clear.
-
Brevity enhances clarity.
-
Favor generic code but not at the expensive of clarity.
-
Always be aware of the trade offs you make.
-
"Premature optimization is the root of all evil" is not an excuse to do stupid things on purpose!
-
Do not leave unreachable ("dead") code behind.
-
Do not comment out code, that is what version control is for.
-
Take advantage of simple language features that afford great power but avoid the esoteric ones, especially in the type system.
-
Learn and use the most advanced features of your favorite text editor. Make sure to configure it to perform as many formatting functions for you as possible, so that you do not have to think about it: remove whitespace at end of lines, add a newline at end of file, etc. If your editor does not support even those basic features, find yourself a better one. :-)
-
Functions should communicate purpose and intent.
Kent Beck tells the history of a piece of code, a single line method in a word processor, that astonished him the first time he saw it. Something like
def highlight(x: X) = underline(x)
. Why write a straightforward alias for theunderline
function, he asked himself? Then he realized the power of this simple abstraction.It just so happened that highlights, at that particular point in time, were implemented with underlinings, but that does not mean that highlights and underlines are the same thing. They do not serve the same purpose. Highlights are semantic, underlines are presentational.
It could be that in the future it is decided a highlight should have, for instance, a yellow background. It would be a trivial change using the method above, taking only but a few seconds to implement. Had they instead used
underline(x)
interchangeably everywhere across the code, one could spend hours looking at each usage site, trying to infer whether the intention of that particularunderline
call was highlight or to actually underline.That is one of the reasons why simple methods like
def isEmpty = this.length == 0
are extremely valuable. No matter how short the equivalent code they capture may be, abstractions that better express intent and purpose are invaluable. -
A word about thin models. In object-oriented design, a object is an implementation of an abstract data type (ADT). Objects must define both a set of values and the operations on them. Objects are not Pascal records or C structs glorified with getters and setters. Classes are not just namespaces for methods.
"Domain Model: An object model of the domain that incorporates both behavior and data. [...] There is hardly any behavior on [thin] objects, making them little more than bags of getters and setters. The fundamental horror of this anti-pattern is that it is so contrary to the basic idea of object-oriented design; which is to combine data and process together. The anemic domain model is really just a procedural style design."
Martin Fowler, Anemic Domain Model
-
Always remember Tip 4:
"Do not Live with Broken Windows. Fix each as soon as it is discovered."
Andrew Hunt and David Thomas, The Pragmatic Programmer
Orthogonality and the DRY Principle is also an interesting read. Full interview.
Go to File, Import Settings..., and select the file settings.jar.
- Editor:
- General:
- Other:
- Strip trailing spaces on Save: All
- Ensure line feed at file end on Save
- Other:
- Code Style:
- Scheme: Scail
- Line Separator (for new files): Unix and OS X (\n)
- Right margin (columns): 100
- Enable EditorConfig support
- Scala:
- Tabs and Indents:
- Use tab character: no
- Smart tabs: no
- Tab size: 2
- Indent: 2
- Continuation indent: 4
- Wrapping and Braces:
- Method declaration parameters:
- Align when multiline: no
- Method declaration parameters:
- ScalaDoc:
- Use scaladoc indent for leading asterisk: no
- Imports:
- Class count to use import with '_': 99
- Sort imports (for optimize imports): scalastyle consistent
- Collect imports with the same prefix into one import: no
- Tabs and Indents:
- Other File Types:
- Use tab character: no
- Tab size: 2
- Indent: 2
- Scheme: Scail
- General:
- Plugins:
- Install JetBrains plugin...
- Scala
- Browse repositories...
- Lines Sorter
- String Manipulation
- Install JetBrains plugin...