February 5, 2016 | code

# Conditionals Are the Screams of Missing Objects

In object oriented programming we dislike the idea of type checking - thing.is_a?(ClassWeKnowAbout). We prefer to just send messages to objects and not care what class they are. In Ruby a conditional - if / else / end - is a type check. Don’t believe me? Consider the following code:

if @user.present?
@user.name
else
"Guest User"
end


At first glance all the code is doing is checking to see if something is present. If it is, we execute one code path, otherwise we do something else. But this is equivalent to:

if true # well, truthy
@user.name
else
"Guest User"
end


true in Ruby is in fact an instance of TrueClass. So really what the code is doing is this:

if @user.present?.is_a?(TrueClass)
@user.name
else
"Guest User"
end


Which is a type check.

Most of us learned to use if from procedural languages and its usage looks completely normal here - but it’s hampering our ability to use objects to their full potential. In this example we used the if statement because a user may or may not be present. nil is a possible value that we have to handle. The easiest thing to do here was to just add the if and go on about our day.

However, conditionals have a tendency to multiply. Imagine the function above was part of a class that was responsible for showing user information. That nil is going to propagate its way through our code.

class UserPrinter
def initialize(user)
@user = user
end

def name
if @user
@user.name
else
"Guest User"
end
end

if @user
else
end
end

def phone
if @user
@user.phone(format: :international)
else
"No Phone Number"
end
end

def email
if @user
@user.send_email
else
# Do nothing
end
end
end


We are trying to send nil a message here - we want to call #name on it but can’t because obviously nil does not have a #name method. But that repeated if / else / end structure is screaming at us that something isn’t right. If we are trying to talk to nil then nil is definitely something. We just don’t have an object or a name for it at the moment.

In OO we want to send messages. OO is about being condition averse. So let’s give this nil a name. In this example no user is meant to be a “Guest User”. That is a thing, an object, a concept. So it should be a class. It’s going to implement the same API a user, because again, we just want to send messages. The implementations of the of the methods may return something or do nothing.

class GuestUser
def name
"Guest User"
end

end

def phone(options = {})
"No Phone Number"
end

def send_email
end
end


Now UserPrinter can become very simple. In the initializer if no user is passed in we default to a GuestUser. We haven’t broken any existing APIs - UserPrinter still works the same as it did before but we have removed four conditionals and made the code much more digestible.

class UserPrinter
def initialize(user)
@user = user || GuestUser.new
end

def name
@user.name
end


This is known as the Null Object Pattern. If you find yourself constantly checking for the nil values, try and replace some of them with a Null Object.