Scala best practice: Eliminate null values from your code

This is an excerpt from the 1st Edition of the Scala Cookbook (partially modified for the internet). This is Recipe 20.5, “Scala best practice: Eliminate null values from your code.”

Problem

Tony Hoare, inventor of the null reference way back in 1965, refers to the creation of the null value as his “billion dollar mistake.” In keeping with modern best practices, you want to eliminate null values from your code.

Solution

David Pollak, author of the book Beginning Scala, offers a wonderfully simple rule about null values:

“Ban null from any of your code. Period.”

Although I’ve used null values in this book to make some examples easier, in my own practice, I no longer use them. I just imagine that there is no such thing as a null, and write my code in other ways.

There are several common situations where you may be tempted to use null values, so this recipe demonstrates how not to use null values in those situations:

  • When a var field in a class or method doesn’t have an initial default value, initialize it with Option instead of null.
  • When a method doesn’t produce the intended result, you may be tempted to return null. Use an Option or Try instead.
  • If you’re working with a Java library that returns null, convert it to an Option, or something else.

Let’s look at each of these techniques.

Initialize var fields with Option, not null

Possibly the most tempting time to use a null value is when a field in a class or method won’t be initialized immediately. For instance, imagine that you’re writing code for the next great social network app. To encourage people to sign up, during the registration process, the only information you ask for is an email address and a password. Because everything else is initially optional, you might write some code like this:

case class Address (city: String, state: String, zip: String)

class User(email: String, password: String) {
    var firstName: String = _
    var lastName: String = _
    var address: Address = _
}

This is bad news, because firstName, lastName, and address are all declared to be null, and can cause problems in your application if they’re not assigned before they’re accessed.

A better approach is to define each field as an Option:

case class Address (city: String, state: String, zip: String)

class User(email: String, password: String) {
    var firstName = None: Option[String]
    var lastName = None: Option[String]
    var address = None: Option[Address]
}

Now you can create a User like this:

val u = new User("al@example.com", "secret")

At some point later you can assign the other values like this:

u.firstName = Some("Al")
u.lastName = Some("Alexander")
u.address = Some(Address("Talkeetna", "AK", "99676"))

Later in your code, you can access the fields like this:

println(firstName.getOrElse("<not assigned>"))

Or this:

u.address.foreach { a =>
    println(a.city)
    println(a.state)
    println(a.zip)
}

In both cases, if the values are assigned, they’ll be printed. With the example of printing the firstName field, if the value isn’t assigned, the string <not assigned> is printed. In the case of the address, if it’s not assigned, the foreach loop won’t be executed, so the print statements are never reached. This is because an Option can be thought of as a collection with zero or one elements. If the value is None, it has zero elements, and if it is a Some, it has one element — the value it contains.

On a related note, you should also use an Option in a constructor when a field is optional:

case class Stock (
    id: Long,
    var symbol: String,
    var company: Option[String]
)

Don’t return null from methods

Because you should never use null in your code, the rule for returning null values from methods is easy: don’t do it.

This brings up the question, “If you can’t return null, what can you do?”

Answer: Return an Option. Or, if you need to know about an error that may have occurred in the method, use Try instead of Option.

With an Option, your method signatures should look like this:

def doSomething: Option[String] = { ... }
def toInt(s: String): Option[Int] = { ... }
def lookupPerson(name: String): Option[Person] = { ... }

For instance, when reading a file, a method could return null if the process fails, but this code shows how to read a file and return an Option instead:

def readTextFile(filename: String): Option[List[String]] = {
    try {
        Some(io.Source.fromFile(filename).getLines.toList)
    } catch {
        case e: Exception => None
    }
}

This method returns a List[String] wrapped in a Some if the file can be found and read, or None if an exception occurs.

As mentioned, if you want the error information instead of a Some or None, use the Try/Success/Failure approach instead:

import scala.util.{Try, Success, Failure}

object Test extends App {
    def readTextFile(filename: String): Try[List[String]] = {
        Try(io.Source.fromFile(filename).getLines.toList)
    }
    val filename = "/etc/passwd"
    readTextFile(filename) match {
        case Success(lines) => lines.foreach(println)
        case Failure(f) => println(f)
    }
}

This code prints the lines from the /etc/passwd file if the code succeeds, or prints an error message like this if the code fails:

java.io.FileNotFoundException: Foo.bar (No such file or directory)

The “Null Object” Pattern

As a word of caution (and balance), the Twitter Effective Scala page recommends not overusing Option, and using the Null Object Pattern where it makes sense. As usual, use your own judgment, but try to eliminate all null values using one of these approaches.

A Null Object is an object that extends a base type with a “null” or neutral behavior. Here’s a Scala implementation of Wikipedia’s Java example of a Null Object:

trait Animal {
    def makeSound()
}

class Dog extends Animal {
    def makeSound() { println("woof") }
}

class NullAnimal extends Animal {
    def makeSound() {}
}

The makeSound method in the NullAnimal class has a neutral, “do nothing” behavior. Using this approach, a method defined to return an Animal can return NullAnimal rather than null.

This is arguably similar to returning None from a method declared to return an Option, especially when the result is used in a foreach loop.

Converting a null into an Option, or something else

The third major place you’ll run into null values is in working with legacy Java code. There is no magic formula here, other than to capture the null value and return something else from your code. That may be an Option, a Null Object, an empty list, or whatever else is appropriate for the problem at hand.

For instance, the following getName method converts a result from a Java method that may be null and returns an Option[String] instead:

def getName: Option[String] = {
    var name = javaPerson.getName
    if (name == null) None else Some(name)
}

Benefits

Following these guidelines leads to these benefits:

  • You’ll eliminate NullPointerExceptions.
  • Your code will be safer.
  • You won’t have to write if statements to check for null values.
  • Adding an Option[T] return type declaration to a method is a terrific way to indicate that something is happening in the method such that the caller may receive a None instead of a Some[T]. This is a much better approach than returning null from a method that is expected to return an object.
  • You’ll become more comfortable using Option, and as a result, you’ll be able to take advantage of how it’s used in the collection libraries and other frameworks.

See Also

  • Tony Hoare’s Billion Dollar Mistake
  • The “Null Object Pattern”