I think you are missing several other options. For example, you could use a language which doesn't have null
. Haskell, Ruby, Smalltalk, many others simply don't have null references, so the problem doesn't even arise.
Another option would be to use a language which does have null
but has explicit null
tracking that allows you to statically ensure that you can never have null references. Spec# does that, for example.
A variant of that option is to use a language which doesn't have null
tracking, but use an external static analyzer, maybe aided by annotations. Code Contracts.NET (an offshoot of Spec#) does that, for example.
Yet another option is to encapsulate the notion of null
ness into a type. One of the problems with null references is that null references are really overloaded. What does getting a null
mean? Some things are meant to fail, e.g. parsers, or looking up a key in a dictionary. It is normal for parsers to be handed invalid input, it is normal to not find a key in a dictionary, that's not an error, and not an exceptional situation.
For this latter case, you can use an Option
type. An Option
is basically kind-of like a collection that is either empty or has exactly one element. (This analogy is actually pretty profound as we shall see shortly.) In the case of the dictionary example, a Dictionary<K, V>
would have a method Option<V> get(K key)
which returns either a "filled" Option
with the value, if the key exists, or an "empty" Option
if the key doesn't exist. The important thing is that the type system can ensure that you have to deal with both cases in your code.
Except that you often actually don't have to deal with both cases. That's where the analogy to collections comes in: an Option
is isomorphic to a list / an array / an iterator / a stream / an IEnumerable
/ some other kind of collection which is either empty or holds exactly one value. This means that Option
can implement whatever collections interfaces your language has, can be a member of the collections framework and can be used with all the collections methods. For example, if you want to transform the value, then you don't need to actually get the value out of the Option
. All collections frameworks have a method to apply a transformation function to every element of the collection (map
in most languages, collect
in Smalltalk, Objective-C and Ruby, select
in SQL and .NET, transform
in C++). What does map
do when you call it on an empty collection? It returns an empty collection. And on a non-empty collection it will return another collection with each element (or in this case the only element) transformed. You don't even have to check whether or not your Option
is empty, you can just call map
on it, and it will do the right thing.
You can chain multiple map
s. You can use flatMap
(SelectMany
in .NET) to chain the use of Option
s. You can use fold
(aka reduce
, aggregate
, accumulate
) to get the value out or alternatively supply a default. And so on.
Apart from being a collection, Option
is also a monad, this makes it easy to work with in languages that have special syntactic support for monadic operations (e.g. Haskell's do
-notation, Scala's for
-comprehensions, C#'s and VB.NET's LINQ query comprehensions).
In Haskell, it is called Maybe a
and has two data constructors called Just a
and Nothing
. (If you are a Java person read Maybe<T>
and Just<T>
and think of data constructors as final subclasses.) In Scala, it is called Option[T]
and has two subclasses Some[T]
and None
. Even Java has one, it is called Optional<T>
.
Obviously, there is a difference between a language like Haskell, which doesn't have null
in the first place, and a language like Java, where Optional<T>
is an afterthought. In the first case, null
s cannot happen, period. In the second case, I would treat null
like any other kind of invalid data: intercept it at the outer boundary of your system, and either throw an exception or sanitize it into an Optional<T>
. (The really nasty thing about a language like Java is of course that a field of type Optional<T>
may itself be null
.)
In Scala, for example, null
mainly exists for compatibility reasons. Pretty much the only way that you can end up with a null
from the Scala language, is with a declared but not initialized mutable field. However, mutable fields are rarely used in Scala, and very un-idiomatic. No method in the Scala library will ever return null
. Idiomatic Scala code will never return null
. Usually, the only way to end up with null
is by calling Java code, and that is typically handled the way I described above: you have a Scala wrapper for the Java code which handles all potential null
s and the rest of the code uses that Scala abstraction and doesn't bother with null
at all.
Yet another option that is however not applicable in all cases is to use a special "empty" object to denote the absence of data instead of null
. A dummy user account for a User
field, an empty string for a String
field, an empty list for a List
field and so on.