Refactoring Ruby: Introduce Null Object
Jon Lunsford
Posted on March 1, 2024
Introduce Null Object is a great refactoring tool when you find yourself checking for nil too often. nil checks are often signs of much larger problems, as it violates Tell, Don’t Ask, and results in defensive, bug prone programming. Let’s take a look at a simple example of refactoring nil checks:
class Movie
attr_accessor :account
def initialize(price)
@price = price
end
def rent
unless account.nil?
account.charge(@price)
end
end
def rentable?
account && account.rentable?
end
def late_fees
account.try(:late_fees) || 0
end
end
This Movie
class is incredibly defensive, it goes to great lengths to ensure its account
actually exists. Why should that responsibility fall on Movie
? It would be much better for Movie
to just assume it has an account OR something resembling an account. Let’s Introduce Null Object:
class Movie
attr_accessor :account
def initialize(price)
@price = price
end
def rent
account.charge(@price)
end
def rentable?
account.rentable?
end
def late_fees
account.late_fees
end
def account
# Introduce Null Object
@account ||= NullAccount.new
end
end
# Object that resembles an 'Account'
class NullAccount
def charge(_price)
"No Charge"
end
def rantable?
false
end
def late_fees
0
end
end
We have gotten rid of all conditions defending against nil
and introduced one that always returns an Account
like object:
def account
@account ||= NullAccount.new
end
Let’s run through the exact steps to take when applying Introduce Null Object:
- Identify conditions that check for nil, that are defending against objects potentially not existing, for example:
def rent
# nil check
unless account.nil?
account.charge(@price)
end
end
- Create an object that acts like the one you are checking for as nil. Specifically, create a new class and define all of the methods that are expected to be there, for example:
class NullAccount
def charge(_price)
"No Charge"
end
end
- Finally, substitute the null object when the expected one does not exist, for example:
Class Movie
...
def account
@account ||= NullAccount.new
end
end
Null objects are great at replacing conditional logic and making code more readable, as you don’t have to think through all of the different branches conditions introduce.
Posted on March 1, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.