Null Errors :: 0x13
The more layered a codebase is, the bigger the gap tends to be between where a coding error is introduced, and where the problematic behavior bubbles up to the surface.
To see what that can look like in practice, let's walk through an example where you try to make sense of a code sample by observing its behavior from the outside first, and then peel back one layer at a time until you reach the source of the problem with its implementation.
Consider the following code sample which checks whether a particular Person
can rent a car:
renter = Person.new(Date.new(2001, 3, 14))
p can_rent_car?(renter)
Even without knowing the specific rules for who is allowed to rent a car and who isn't,
Ruby conventions imply that can_rent_car?
will return a true
or false
value.
Upon running the code, you discover that it does not behave as expected.
Instead, it raises an exception with the following error message:
% ruby rental.rb
rental.rb:15:in 'Object#can_rent_car?':
Cannot verify renter eligibility. Age unknown. (RuntimeError)
from rental.rb:23:in '<main>'
This prompts you to take a look at the can_rent_car?
method to see what's going in within it:
def can_rent_car?(person)
if person.age == :unknown
raise "Cannot verify renter eligibility. Age unknown."
end
person.age >= 21
end
Aside from it being somewhat of a questionable practice to raise an exception from a conditional method of this sort in Ruby, there's nothing obviously wrong with this code.
It effectively tells you that a Person
is expected to be 21 or older in order to rent a car,
and that this method ought to only ever be called with a Person
that has a known age.
But in the earlier code sample, the Person
object was initialized with a Date
that
was presumably their birthdate (2001-03-14), so... there has to be something wrong
elsewhere in the codebase.
So let's pull up the Person
class definition and take a closer look:
require "date"
class Person
def initialize(birth_date)
@brith_date = birth_date
end
def age
@birth_date ? ((Date.today - @birth_date) / 365.0) : :unknown
end
end
Q1: What tiny mistake was made in the definition of the Person
class which is causing age
to incorrectly report :unknown
,
even when a birth date is provided?
# [ANSWER 1] #
There is a typo in the `initialize` method:
@brith_date should be @birth_date
Q2: Suppose that you could take for granted that a birth date
is a required attribute of a Person
object, and that when it is unknown,
it must be explictly set to :unknown
.
How could you revise the code to be less prone to this particular type of coding error?
# [ANSWER 2] #
There are a number of different possible ways to solve this problem.
One potential solution would be to make use of pattern matching:
class Person
def initialize(birth_date)
@birth_date = birth_date
@birth_date => Date | :unknown
end
def age
return :unknown if @birth_date == :unknown
(Date.today - @birth_date) / 365.0
end
end
Any approach that would cause the exception to be raised from the
Person constructor where it actually occured would prevent this type
of mistake from going unnoticed and bubbling up elsewhere in otherwise
correctly written code.
You can preorder now to be among the first to gain access when it is released.