June 17, 2013
MarkBernstein.org
 

Null Object (wonkish)

In general, Tinderbox follows the once-universal C/C++ convention that, when someone asks for an object that doesn’t exist, it receives nil or zero. For example, if we have a Note *note and we ask for the next note Note *next=note->Next(), we’ll get back nil if the note is the last note in the document.

This is the way C was always intended to work. Problems arise because nil isn’t really an object. We can ask a Note to draw itself, node->Draw(), but we can’t ask nil to do anything. And if we do, the program will crash. So, we habitually write if (node) node->Draw(), so that we don’t dereference nil.

The modern way to do this is to create a Null Object — a special class of Note that we might name NotANote. When you ask for the note that follows the last note, you get an instance of NotANote. If you try to draw a NotANote, nothing is drawn. If you ask for its word count, you get back “0”. You can do anything with a NotANote that you can do with a Note, and you won’t crash.

Tinderbox doesn’t use Null Object a lot. Tinderbox was written in 2002; Design Patterns dates back to 1995, but it took some time for the word to spread. Null Object makes the code simpler, and eliminates crashes.

The problem is that existing code checks for nil to know when it’s reached the end:

Note *next=start;
while (next) {
	next->Mill();
	next->Drill();
	next-next->Next();
	}

Switching from nil to Null Object means finding every place that checks for nil and replacing it with something like next->IsValid() or !next->IsNotANote(). And while Null Object is modern, the modern approach to refactoring strongly encourages incremental, testable changes, not a change that will require examining every line of code and making perhaps 10,000 individual edits, each of which must be correct, in a single leap.

C++ does provide a loophole that might help. NotANote could redefine ==, !=, !, and a bunch of casts, in order to tell white lies. For example, if we have written

if (next==nil) {….

We could arrange for NotANote to say “OK: you want to know if I’m the same object as nil. And I am! Really! Who’re you going to believe, me or your eyes? Trust me! I’m equal to nil!”

This could allow a nice, incremental refactoring. But it’s also a can of worms: this kind of chicanery with overloading has a bad reputation. It’s easy to overlook something, or to return a bool when the compiler was expecting an int — a distinction that matters only on alternate Tuesdays.

I’m betting that I’m not the only fellow looking at this refactoring question. Fowler in Refactoring and Kerievsky in Refactoring to Patterns offer sensible mechanics for a local refactoring where you can examine and replace every null test, which is what you’d typically do. But some of these classes provide ubiquitous abstractions for Tinderbox, and replacing all those tests in one fell swoop is not a great idea. Wisdom appreciated: Email me.