Avoid nil-checks. Code confidently. Be happy.

This post is about pretty code (with examples in Ruby), plus how to get nil out of the way and be more confident.

Does this code look familiar?

user && user.ask_for_email

How about this?

<% if user && user.admin? %>
  Hello, Admin!
<% end %>

Those are very common nil-checks. As developers they make us unsure – must we test all edge cases in our code and give up clearness of algorithms for the sake of edge-case handling?

Eventually everything is so messed up we don’t have the time to worry about how pretty our code is, and that’s not fun. Programming should be fun!

Look at this example.

def message(user)
  if user.admin?
    "Hello, Admin!"
  else
    "Hello #{user.nickname}"
  end
end

You might think: “Meh, it’s easy to read, it’s not bad code”, and you would be right. But consider the nil! If user is nil, our code will break. Let’s make it more robust. Robustness is good right? Handle all the edge cases!

X-all-the-things-template

def message(user)
  return "Hello, you" unless user
  if user.admin?
    "Hello, Admin!"
  else
    "Hello #{user.nickname}"
  end
end

Well, that wasn’t so bad. But then we remember there’s another kind of greeting.

def message(user)
  return "Hello, you" unless user
  if user.admin?
    "Hello, Admin!"
  elsif user.speaks_spanish?
    "Hola #{user.nickname}"
  else
    "Hello #{user.nickname}"
  end
end

Okay that could be a switch statement, or it could be solved in another way, but still, the idea is the same: use conditionals and make sure all cases are handled.
Now consider this solution.

class Admin < User
  def greet
    "Hello, Admin!"
  end
end

class User
  def greet
    if speaks_spanish?
      "Hola #{nickname}"
    else
      "Hello #{nickname}"
    end
  end
end

class NullUser
  def greet
    "Hello, peasant!"
  end
end

def message(user || NullUser.new)
  user.greet
end

Isn’t that nice? We actually moved the conditional logic from ifs into objects, using inheritance and null objects. It’s a more object-oriented approach, which turns out to be better (sometimes).

The argument for the object-oriented approach is that asking objects about their state, and then doing things to that object based on the response, reduces the encapsulation of the object, as some of its behavior has leaked to the outside world.

If you are interested in this, you might want to check out “Tell, Don’t Ask” and Sandi Metz’s awesome talk: “Nothing Is Something“.

Moving on to a new example.

def show_items()
  return if ItemService.items.nil?
  ItemService.items.each { |i| puts i }
end

We wouldn’t want to call each on nil, so we conditionally return early to prevent an error. But that should ring a bell, there must be a better way:

def show_items()
  ItemService.items.to_a.each { |i| puts i }
end

That’s better! The code is more confident.

If the object you are working with is not your own, such as inside a gem, create a “barrier” in your code by declaring what you expect other objects to be. In this case, we expect something that looks like an array. If that code ever breaks, we will know where the error is, and why it happens – you won’t need to read the whole system to deduce that you were expecting an array, and got something else.

On the other hand, if you own the code, it’s easier to just be consistent with the things you return.

Class ItemService
  def self.items
    @@items || []
  end
end

def show_items()
  ItemService.items.each { |i| puts i }
end

A good rule is: Your methods should always return the same type of value. If a method can return a collection, always return a collection, and use the empty array to represent nothing. If a method returns a string, return the empty string, not nil. If a method returns an object User, return NullUser, not nil.

Identifying the nil-check is only one of the many tools at your disposal when it comes to code refactoring. There are many, and some actually have a name! These are the essence of “Design Patterns” — solid, known solutions to a set of common problems. Because Ruby is an object-oriented language, “Object-Oriented Design Patterns” might interest us twice as much (as Ruby developers).

Design Patterns are great, but I think the most important thing — at least for web applications — is for the code to be easy to read. For me, simple code is pretty and complex code is ugly. Well, sure, it can be subjective, there’s no magic formula to calculate the prettiness percentage of your code (someone let me know if there is…).

I think it’s one of those things that just comes with time; looking at a lot of pretty code and a lot of ugly code.

It’s important that the code should not always be your own code! For example, you can read the source of the gems you use, just take a look at a class or two, it doesn’t have to be the whole thing.

David Heinemeier Hansson, the creator of Ruby on Rails, gives a perfect example of this when he explains how, as he learned photography, he could more easily tell bad photos from good ones. He began to understand why bad photos are bad, using tools like lighting, focus and exposure. With code it’s the same: be open minded, understand other people’s points of view and compare them with your own. Eventually you’ll be able to draw a more well-defined line between pretty code and ugly code.

Nevertheless, I don’t think it’s possible to ever have a perfect line of code, after all, writing is a creative process, and rules are meant to be broken. DHH sums it up best, “Programming is a lot more like french poetry than it is like physics”.

  • Fede

One thought on “Avoid nil-checks. Code confidently. Be happy.

Leave a Reply