Null Is A Vile Beast ~ Thu, 22 Aug 2013 15:04:48 +0000
Since my last post on Java I have been learning the Spring web framework. The Restlet detour was very enlightening, and I do prefer that framework, but everything I deal with (insofar as Java web applications) at work is based on Spring. So I want to revisit that post from a Spring based approach at some point in the future. This post, however, is about avoiding the use of null
.
While working in Java I have developed a renewed love of types. After working in dynamic scripting languages for so long it is refreshing to write code where you know, as you're writing it, the exact representation of the data. Sure, you can institute methodologies to suggest types in dynamic code; but those are easily broken for any reason. For example, this bit of JavaScript:
(function() {
var aNumber = 0,
foo = function(){};
foo = function(x) {
if (x > 0) {
return x*x;
} else {
return "Invalid number";
}
};
aNumber = foo(2);
console.log(aNumber); // 4
aNumber = foo(-1);
console.log(aNumber); // "Invalid number"
}());
Someone looking at the variable definitions would expect aNumber
to always be a numeric value, but someone has come along and given it the possibility of being a string value as well. This isn't necessarily a bug, but it can turn into one fast, and be difficult to track down. Explicitly types solves this problem before the code is ever executed -- it just won't compile.
But what does this have to do with null
? Let's switch over to Java:
public class NullSucks {
public static Integer foo(int x) {
if (x > 0) {
return x*x;
} else {
return null;
}
}
public static void main(String[] args) {
Integer aNumber = NullSucks.foo(2);
System.out.println("aNumber = " + aNumber); // "aNumber = 4"
aNumber = NullSucks.foo(-1);
aNumber = aNumber * 2; // Null pointer exception is thrown. Program end.
System.out.println("aNumber * 2 = " + aNumber);
}
}
As the comments indicate, this program will compile just fine, but it will "crash" at line 15 when it is run. Clearly, we can see that we should expect a numeric value or a null value by reading the implementation of foo
and could have coded around it. But if all we have to go on is the method declaration, public static Integer foo(int x);
, then we would expect an instance of Integer
every invocation. So, returning null
allowed us to write a "valid" program, but it is a clear mistake.
We can't get rid of null
. It's something we have to live with as a "bad part" of Java. And we will have to deal with it in third-party code that we don't control. But we can avoid using it in our own code.
Google has a rather nice library named Guava. Included in this library is a class named Optional. Optional is merely a container for other types. The benefit of Optional is that it forces you to check for the existence of a value before using it. Let's rewrite the previous example using Optional:
import com.google.common.base.Optional;
public class NullSucks {
public static Optional foo(int x) {
Optional result = Optional.absent();
if (x > 0) {
result = Optional.of(new Integer(x*x));
}
return result;
}
public static void main(String[] args) {
Optional fooResult = NullSucks.foo(2);
Integer aNumber = (fooResult.isPresent()) ? fooResult.get() : 0;
System.out.println("aNumber = " + aNumber); // "aNumber = 4"
fooResult = NullSucks.foo(-1);
if (fooResult.isPresent()) {
aNumber = aNumber * 2;
System.out.println("aNumber * 2 = " + aNumber); // "aNumber * 2 = 4"
} else {
System.out.println("invalid result");
}
}
}
Yes, the use of Optional has added a few more lines of code, but we have gained a tremendous benefit -- our program works as expected every time. Hopefully you can see from these contrived examples how using Optional will lead to more stable code and easier to use APIs. Of course not all methods need to return an Optional; but if the method has any chance of returning an "absent" (i.e. null
) value, I guarantee you will like that method better if it returns an Optional.
As an aside, I'm going to leave you with a Scala example. Scala has this concept built into its core library:
object FooBar {
def main(args: Array[String]) {
def foo(x: Int): Option[String] = {
if (x > 0) {
return Some("You entered: %d".format(x))
} else {
return None
}
}
val bar = foo(42)
val baz = foo(-42)
if (!bar.isEmpty) {
println("bar = `%s`".format(bar.get))
}
if (baz.isEmpty) {
println("baz is empty")
}
}
}
Comments
Ben Simpson said (2013-08-22 23:15:44 GMT):
I'm glad you linked the Scala example to show how some languages represent null as an object. Ruby supports this and I like the approach. It even has a try method that is useful for method chaining to abstract the pattern of checking for null and optionally continuing:
user.try(:password).try(:valid?)
James Sumners said (2013-08-23 00:54:47 GMT):
I'm not going to pretend that Scala example is definitive, or even good. I've only been learning Scala since Monday. But you should not take from it that Scala represents all null values in terms of Option instances. Option is merely available in the standard library and default namespace. Null is also available.
Here's an article that shows cleverer uses of the type -- http://blog.danielwellman.com/2010/08/more-thoughts-on-scalas-option-class.html .
If you read the article that one references you'll see and argument that this pattern is useless because all you're doing is checking for null with a different name. That guy has missed the point; he even argues that you should let you program bomb out with the null reference exception. The point of this pattern is that it 1) makes it clear when null is a potential result and 2) forces you to deal with it in some way other than "fuck it I don't care."
As for your Ruby example, I don't like that '?' syntax. Groovy has a similar method for avoiding null references. I have found that it is not helpful at all. It merely leads to code that assumes "well, I used the languages null check operator so I should be a-okay" and that's almost never the case (it's just as good as assuming you'll never get null).
Ben Simpson said (2013-09-06 12:00:27 GMT):
Check out number 5 on this list: http://java.dzone.com/articles/10-subtle-best-practices-when
Mirrors your advice. Also, it looks like Optional is included with Java 8.
As for predicate methods in Ruby, they can be nice but that example could have used anything. Feel free to disregard:) The biggest issue to this pattern is the law of Demeter violation. Its tough when debugging because any of those methods could return null. Its a nice shorthand anyway.
James Sumners said (2013-09-06 12:47:28 GMT):
That's a pretty good article. I learned a couple of new things from it. Thank you for linking it.
Yeah, Java 8 is going to pretty damn nice. Too bad I won't really be able to use it for a long time. Jerkwads are still standardizing on Java 1.6 (which is no longer supported itself!).