One of my pet peeves in programming is null checks. Many codebases are littered with them and, as a result they are often very hard to understand.
It's easy enough to complain about null checks, but it's harder to root out all of the places they occur and find alternatives. The typical advice is to move your code toward using the null object pattern or to just check for null immediately and make sure that you don't pass nulls along in your program. After all, we do often need to retrieve objects and sometimes they aren't there.
Let's re-examine that.
Do we really need to retrieve or find objects in our programs?
The other day, I saw this example on StackOverflow:
Person person = dataSource.getPersonById(personId);
if (person != null) {
person.setPhoneNumber(phoneNumber);
dataSource.updatePerson(person);
}
This looks like a typical case where we need a null check. We attempt to find a person from a dataSource and we could get a null return value. We could change the code to return a null object, a Person object that simply does nothing and that could solve the problem. The code would look exactly the same except that it would be missing the null check. The setPhoneNumber function would simply do nothing, and the updatePerson function would have the good sense not to add the null object to the database.
Yes, would could do that, but it is a bit of work. There is an alternative that we can use if we get ourselves out of the mindset of asking for objects.
Consider this bit of Ruby code:
data_source.person(id) do |person|
person.phone_number = phone_number
data_source.update_person person
end
The person method accepts an id for a person and a block. If the person is found the block is called with the person. Otherwise it isn't. Elegant, eh? And, there are no nulls.
Lots of luck doing this in a language without blocks or lambdas. I think that when the history of computing is finally written, one of the chapters will be about how much insanity thrived in the era before blocks were considered mainstream. In fact, I think we can argue that null checks became common precisely because most languages haven't had blocks until recently.
Imagine doing this in Java without lambdas:
dataSource.person(id, new Action<Person>() {
public void act(Person person) {
person.setPhoneNumber(phoneNumber);
dataSource.updatePerson(person);
}
});
My eyes bleed. My soul too. It's no wonder why null-littered code is common in today's Java.
This teller method pattern may seem like a special case, but it isn't. One of the core ideas in object-orientation is that it is better to tell than to ask. When we tell someone to give someone else a thing, it either does or it doesn't. There's no need to check for an error.
So, whenever you receive null from an API you've written, don't complain. After all, you asked for it. Consider telling the result to someone instead.
'Tellers', otherwise known as the Maybe/Option monad.
Posted by: Erik | June 17, 2013 at 06:14 AM
Hmm.. you still get something from Maybe. The getter cases the result. Not sure I see the connection. Elaborate?
Posted by: Michael Feathers | June 17, 2013 at 06:17 AM
In your first example, has to check for a null object, before it has the "good sense" not to add it to the database. And the Ruby block seems to require an equivalent check, but it's one you can't see. [I think.] So are you removing null checks, or hiding them so that they won't offend you?
Posted by: Pattern-chaser | June 17, 2013 at 06:28 AM
Sorry, I didn't realise anything in angle brackets would get chomped. :( Previous query should begin "In your first example, [updatePerson] has to check...".
Posted by: Pattern-chaser | June 17, 2013 at 06:31 AM
Consider a Maybe object with a method "bind" that takes a block. If the Maybe object contains a value, "bind" passes the value to the block, while if the Maybe object is empty, the method does nothing.
Posted by: Weavejester | June 17, 2013 at 06:39 AM
Why would you even allow the program to continue when the person you want to update doesn't exist in the database?
Posted by: Robert | June 17, 2013 at 06:45 AM
In Scala at least, rather than explicitly checking a Maybe for Some or None, you can just do maybe.map(). That's exactly the form of the "teller" code in this post.
Posted by: philandstuff | June 17, 2013 at 06:57 AM
That should read maybe.map( [transform fn] ) above..
Posted by: philandstuff | June 17, 2013 at 06:58 AM
Erik, Michael,
I wanted to point out that Michael suggests that one would pattern match ("case") the Option (I'm going to just use the Scala vernacular rather than the Haskell since this post has some Java). The complaint is that matching is too similar to a null-check, even if the match is typesafe with compiler checks for exhaustive matching.
While pattern matching is an option, what we'd really prefer to do is something like fold (also known as the "visitor" pattern) on the Option, providing a transformation for the Some case, and a default value for the None case. I don't know how well code will render in these comments, but it would look something like this:
src.person(id).fold(defaultVal) { p => valFor(p) }
What Michael is further proposing is not to expose the Option to the client at all, but to make the fold internal. But notice that the implementation in the article is rigidly imperative (the "void" return). We get a more compact call,
src.person(id) { p => unsafePerformEffect(p) }
but it's terribly restricted to side-effects.
In this sense, we actually provide the client /more/ functionality by just exposing the Option to them. The Option isn't a liability. It's an extremely nice point of abstract flexibility. Option not only comes with the general fold, but also great functions like map, flatMap, and many others.
This does not prevent an API from providing specialized "loan" pattern functions (which is what this "tellers" idea seems to me) to reduce a modicum of Option boilerplate, but I'd recommend that the Option be exposed raw as well because it's very useful. This "teller" solution is highly specialized for side effects. I'd prefer effect tracking for this, but that's another post.
Posted by: Sukant Hajra | June 17, 2013 at 07:08 AM
+1 for Sukant's comments on option types. I've recently blogged about this exact point. http://proseand.co.nz/2013/06/05/optiont-null/
Posted by: Josefusbarnabas | June 17, 2013 at 08:56 AM
The way you could do this in a language that doesn't have closures is return a collection of 1 or 0 items and use foreach.
Posted by: Arseny Kapoulkine | June 17, 2013 at 08:59 AM
Java isn't the best of all languages for this, but
for (String name: getName("hello")) {
// do something with name
}
if getName() returns an Option that is iterable.
http://codemonkeyism.com/for-hack-with-option-monad-in-java/
Posted by: Codemonkeyism | June 17, 2013 at 09:47 AM
A bit of a limited example, what do you want to do in case the person wasn't found. Would you not like to tell the user that the Person was not found? How would you handle that?
Posted by: Gustavo | June 17, 2013 at 10:18 AM
@Gustavo: In this case the author needs the Either/Choice monad :)
Posted by: Vasily Kirichenko | June 17, 2013 at 10:24 AM
The thing that worries me about this example is that it seems there should be an `else`. If you looked up a specific id, then if it does not exist you really want to do something. Any option/Elvis operator-style/[single method] teller solution is just going to bury the programming error. Probably what we want here is a checked exception, or similar. If not, than perhaps a second block for the else-clause would be in order. Now we have a gaggle of method arguments. Seems like anonymous inner classes would be the perfect solution, if only Java's syntax didn't suck all over.
Posted by: Thomas Hawtin | June 17, 2013 at 11:16 AM
What if you need to get & use multiple values at the same time? Are you forced into nested blocks?
Posted by: Matthew O'Connor | June 17, 2013 at 12:54 PM
Note: This comment system seems to eat less than and greater than symbols, I've replace them with lt and gt, you'll have to mentally do the conversation back to make these examples syntactically correct.
There's also the maybe function to go along with the Maybe monad.
E.G.
x lt- getPersonById id
let person = maybe defaultPerson id x
updatePerson $ person { phoneNumber = number }
or if you wanted to do something closer to what's presented in the article you could take advantage of the Applicative class:
x lt- getPersonById id
updatePersonsNumber number lt$gt x
There's some minor gotchas in that you couldn't do the update DB call inside of the Maybe instance, but if you redefined getPersonById to return something of type:
MaybeT IO
then the whole thing simplifies to:
person lt- getPersonById id
liftIO . updatePerson $ person { phoneNumber = number }
and it automatically results in IO Nothing in the case that getPersonById was Nothing.
Posted by: R. Kyle Murphy | June 17, 2013 at 01:00 PM
Matthew: No, you can accumulate in into a collection: data_source.person { |p| collection.add(p) }
Posted by: Michael Feathers | June 17, 2013 at 01:06 PM
Re the 'else' case..
Context matters. Sometimes continuing silently is exactly the right thing to do. Granted, it's hard to see in this example. To stretch it, imagine that we're setting alternative phone numbers in a background job. We don't expect all of the IDs to be associated and we're okay if they aren't.
The idea that there is one right way to handle errors is a fallacy. I'm reminded of it every time I see someone yell "fail fast." "Fail fast" only works for one class of systems.
Posted by: Michael Feathers | June 17, 2013 at 01:07 PM
Michael: But, then, can't values of person still be null potentially causing the same problem?
Posted by: Matthew O'Connor | June 17, 2013 at 01:38 PM
I use a general helper method I call `present` which is the exact same thing as the Ruby `tap` method except it calls the block (lambda) only when the subject is present (not some equivalent of nothing). This way you have a general purpose means of doing the same but without having to implement anything (i.e. you wouldn't have had to implement the `person` finder.
dataSource.getPersonById(personId).present(function(person){
person.setPhoneNumber(phoneNumber);
dataSource.updatePerson(person);
});
Posted by: Mario T. Lanza | June 17, 2013 at 01:54 PM
In smalltalk you can even do this:
dataSource
personById: 'id'
ifPresent: [:person | ... ]
ifAbsent: [ ... ]
Posted by: Zeroflag | June 17, 2013 at 01:59 PM
Matthew: No, because the block is only called if there is a real person object to pass to it.
Posted by: Michael Feathers | June 17, 2013 at 02:09 PM
Mario, Zeroflag: Very nice. Again, it points out how the absence of block in languages has led to null checking.
Posted by: Michael Feathers | June 17, 2013 at 02:10 PM
Michael: Sorry, not values of person, but the values stored inside person (name, age, etc.)
Posted by: Matthew O'Connor | June 17, 2013 at 02:24 PM