One thing that we learn from exploring the metaprogramming features of Ruby is that nothing is a deep concept.
Talk about an exhausting Monday!
You’ve spent the entire day pair programming with Bill, your excellent, experienced, and sometimes exasperating buddy. The two of you are putting the final touches to the Alarm Manager—a useful piece of Ruby code that sends alerts to your colleagues when the main company-wide application has a glitch.
The Alarm Manager is just a short program, but it sports a flexibility to rival Twitter. It can send different kinds of alarms to any remote device, from brand new iPhones down to vintage pagers. Here is the cornerstone Alarm Manager class:
The Alarm#device method reads the device associated with the current user from a CONFIGURATION constant. Your program can send alarms to any device, as long as the device can flash a light and ring. The send_default method rings ten times, send_discreet only rings once, and send_silent just flashes the light for a while.
You’re just one click away from a working Alarm Manager. After configuring the system with the devices of all your colleagues, you and Bill fire the last functional test... and watch in horror as it fails.
Luckily, it doesn’t take much to find the cause of the error: Bob, your absent-minded colleague, recently lost his cell phone. Now the CONFIGURATION class doesn’t return any device for Bob—instead, it returns nil, Ruby’s value for an uninitialized object reference. When Alarm attempts to call ring or flash on nil, the call fails with a NoMethodError.
Uninitialized references are a common nemesis of object-oriented programmers. (Just ask a Java coder how many NullPointerExceptions she’s seen in her life!) How can you avoid this problem?
As a first attempt, you and Bill check the object returned by device to ensure that it’s not nil. For example, Alarm#send_default becomes:
It just takes that single line of code to push old Bill into one of his legendary bursts of complaining. “I don’t want to add an extra if to each and every method!” he exclaims. “I think I know a better way to fix this problem. Let me show you something...”
Bill is eager to show you a magic programming spell known as Null Object. A Null Object is just a regular object whose methods do nothing.
Bill grabs a scrap of paper and draws a picture to show you how a Null Object is supposed to work. The idea is that you can replace an uninitialized reference with a Null Object. You can then call methods on the object, and the object will trash your messages without raising an error.
In some situations, “doing nothing” might mean returning zero, writing a zero-length file, or returning an empty string. In the case of your Alarm class, “doing nothing” just means that both the flash and ring methods are empty:
Alarm#device can now return the configured device if it exists, and a NullDevice if it doesn’t:
If you’re new to Ruby, you might find the line of code in device confusing. Bill explains that this idiom is called a Nil Guard. Alarm#device only returns CONFIGURATION.current_user.device if it’s not nil.
If CONFIGURATION.current_user.device is nil (or false), then Alarm#device returns a NullDevice. Now, no matter what happens, the device method will always return something that you can safely call ring and flash on—so you can avoid littering the callers of Alarm#device with defensive ifs.
However, grumpy old Bill is not satisfied yet. “This Null Object looks really Java-ish,” he mumbles, frowning. “I think that we can make it more like idiomatic Ruby. Here is how...”
Playing Ruby’s Strengths
In most languages, an uninitialized object reference is just a big arrow pointing at nothing. On the other hand, in Ruby there is no such thing as “nothing.” The nil value is actually a regular object—the sole instance of NilClass:
Another powerful feature of Ruby is that classes are never closed. You can always re-open and modify an existing class, NilClass included. This means that there is no need for a NullDevice class. Instead, you can define flash and ring on NilClass itself:
Now you don’t need to check whether CONFIGURATION.current_user.device is nil. In fact, nil itself has become a Null Object: you can call flash and ring on it, and it will do nothing. Wonderful!
However, you probably don’t want to pollute NilClass with your own domain-specific methods. As Bill is ready to observe, you can find a more elegant solution in yet another Ruby magic spell: a special method named method_missing. If you implement method_missing, it will intercept all calls to methods that don’t exist. Just replace NilClass#flash and NilClass#ring with method_missing, and method_missing will take care of all calls to nil:
Now you can say that flash and ring are Ghost Methods, because the caller thinks they exist, but actually they don’t.
Bill draws a picture to show you how your new Null Object works:
If you use Ghost Methods instead of flesh-and-bones methods like flash and ring, you also get another advantage for free: if you add new methods to your devices (say, a beep method), then you don’t need to add the same methods to nil. Instead, nil will simply ignore calls to any method that it doesn’t know about.
Beyond Null Objects
Just as you’re about to turn off your computer and call it a day, you and Bill get one of those last-minute change requests. Your boss wants an Alarm#send_urgent method for devices that have a controllable light:
“This is wonderful!” Bill exclaims, smiling for the first time this week. (For a moment, you think that he’s slipped away for good). “If we’d stuck with the NullDevice class, now we’d also need a NullLight class, to call change_to_red and turn_off. Instead, with our nil-based solution, we don’t have to write a single line of code. Here, let me draw a picture...”
The trick here is that Ruby has no such thing as a “void” method. Instead, each and every method always returns a value. In particular, an empty method always returns nil. Now, look at your NilClass again:
NilClass#method_missing is empty, so it returns nil. This means that any call to a Ghost Method on nil returns nil itself. You can then call another Ghost Method on nil, chaining arbitrary calls like in the case of device.light.change_to_red. When used this way, a Null Object such as nil is also called a Black Hole. A Black Hole sucks your method calls into an infinite depth of Null Objects, avoiding the problems associated with uninitialized references.
On the other hand, Black Holes can cause their own share of trouble. After all, NoMethodErrors are there for a reason: they help you spot bugs in your code. A Black Hole can mask those bugs and make it more difficult for you to notice when things go wrong. Bill’s recommendation is that you should try Black Holes yourself, and decide whether they work for you. After all, there is at least one popular language (Objective C) where all null reference are Black Holes by default.
However, this interesting discussion can wait for another day. Today, you and Bill can finally slip out of the office and dump yourselves in the nearest pub!
Paolo Perrotta has more than ten years of experience as a developer and writer. He worked for domains ranging from embedded to enterprise software, computer games, and web applications. These days, Paolo coaches agile teams for a huge Internet fashion shop and teaches Java to developers throughout Europe. He lives near Bologna, Italy, with his girlfriend and a cat. He likes the Ruby language so much, he wrote a book about it.