Wednesday, October 14, 2015

Null is optional

Null reference is commonly used to express the absence of value. It has a certain drawback in many languages - the ones that allow nullable references without explicit syntax. Let's take a look at the following java method:

User findUser(String name);

What happens when a user with the given name can't be found? Returning null expresses the concept of an absent value in probably the simplest way, but it has a specific disadvantage: the possibility of the null reference is implicit, the interface doesn't reveal this. Why is that a big deal? Null references need handling, otherwise NullPointerException may occur at runtime, a very common error:

findUser("jonhdoe").address(); // NullPointerException at runtime
Generally not all the values are nullable in a system. Indicating where exactly handling is needed is useful to reduce both risks and unnecessary handling effort. Documentation or meta code can communicate nullability, but they're not ideal: possible to miss and lack compile safety.

Some languages don't allow null references - or only by specific syntax. For others there is an alternative to address the issue by making use of their type system: the option type. It's useful to know about this pattern as it can be easily implemented in most languages - in case they don't contain it already. Let's take a look at java 8's Optional for instance. "A container object which may or may not contain a non-null value."

Optional<User> findUser(String name);
This interface communicates clearly that a user may not be present in the result and the client is forced to take that into account:
// findUser("jonhdoe").address(); compile error!
Handling the absent value:
Optional<User> user = findUser("johndoe");
if(user.isPresent()) {
 user.get().address();
}
This example is intentionally kept somewhat similar to how null references are often handled. A more idiomatic way of doing the same:
findUser("johndoe").ifPresent(user -> user.address());

It's interesting to consider the effects of the pattern in the wider context of an application. With consistent use of Optional, it is possible to establish a powerful convention of avoiding the use of null references altogether. It transforms an interface from:

interface User {
  String name();
  Address address();
  BankAccount account();
}
to:
interface User {
  String name();
  Address address();
  Optional<BankAccount> account();
}
The client can see a user may not have a bank account and can assume it always has a name and address - the convention at play here. Such practice facilitates changes also: eg if address becomes optional at some point in the future all client code will be forced to conform. The code examples so far presented the effects on method signatures, the same benefits apply to class fields or local variables:
class Address {
  String city;
  Optional<String> street;
}

It's particularly handy for functional programming and also provides some syntax sugar that simplifies code in many scenarios:

Optional<String> foo = Optional.ofNullable(thirdPartyApiThatMayReturnNull());
String foo2 = foo.orElse("No value.. take this default one.");
String foo3 = foo.orElseThrow(() -> new RuntimeException("I really can't work without a value though"));
thirdPartyApiExpectingNullable(foo.orElse(null));
if(foo.equals(foo2)) { // no NullPointerException, even if foo is absent
  // ...
}

Conclusion

Avoiding implicit nulls as much as possible can do wonders to a codebase, makes it a much safer place to be. Disadvantages? Patterns like the option type work well in most cases, but as with everything there are exceptions: it consumes heap object, needs to be considered in extreme cases. There may be specific scenarios where such practice doesn't deliver real value or is impractical. 3rd party code may also force the use of null references.

An example of the use of optional: the java 8's Stream API

No comments:

Post a Comment