Saturday, 2 January 2016

Scala Best Practices

1. SHOULD NOT declare abstract "var" members

It's a bad practice to declare abstract vars in abstract classes or traits.
Do not do this:
trait Foo {
  var value: String
}

Instead, prefer to declare abstract things as def:
trait Foo {
  def value: String
}

// can then be overridden as anything
class Bar(val value: String) extends Foo

The reason has to do with the imposed restriction - a var can only be overridden with a var. The way to allow freedom to choose on inheritance is to use def for abstract members. And why would you impose the restriction to use a var on those that inherit from your interface. def is generic so use it instead.

2. MUST NOT use Option.get

You might be tempted to do this:
val someValue: Option[Double] = ???

// val result = someValue.get + 1
Don't ever do that, since your trading a NullPointerException for a NoSuchElementException and that defeats the purpose of using Option in the first place.
Alternatives:

1. using Option.getOrElse
2. using Option.fold
3. using pattern matching and dealing with the None branch explicitly
4. not taking the value out of its optional context

As an example for (4), not taking the value out of its context means this:
val result = someValue.map(_ + 1)

Instead of working with Any, think about the generic type you want and the set of sub-types you need, and come up with an Algebraic Data-Type:

sealed trait JsValue

case class JsNumber(v: Double) extends JsValue
case class JsBool(v: Boolean) extends JsValue
case class JsString(v: String) extends JsValue
case class JsObject(map: Map[String, JsValue]) extends JsValue
case class JsArray(list: Seq[JsValue]) extends JsValue
case object JsNull extends JsValue

Now, instead of operating on Any, we can do pattern matching on JsValue and the compiler can help us here on missing branches, since the choice is finite.


3. SHOULD NOT define case classes nested in other classes

It is tempting, but you should almost never define nested case classes inside another object/class because it messes with Java's serialization. The reason is that when you serialize a case class it closes over the "this" pointer and serializes the whole object, which if you are putting in your App object means for every instance of a case class you serialize the whole world.
And the thing with case classes specifically is that:

1. one expects a case class to be immutable (a value, a fact) and hence
2. one expects a case class to be easily serializable

Prefer flat hierachies.

4. MUST NOT include classes, traits and objects inside package objects

Classes, including case classes, traits and objects do not belong inside package objects. It is unnecessary, confuses the compiler and is therefore discouraged.
For example, refrain from doing the following:

package foo
package object bar {
  case object FooBar
}

The same effect is achieved if all artifacts are inside a plain package:

package foo.bar
case object FooBar

Package objects should only contain value, method and type alias definitions, etc. Scala allows multiple public classes in a single file, and the convention is to have the first letter of the filename be lowercase in such cases.


Reference:
https://github.com/AlvinCJin/scala-best-practices/blob/master/sections/2-language-rules.md

No comments:

Post a Comment