Don't Fail at Failure in Ruby

Silent failures are difficult to debug -- not only is the error not noisy and obvious, but it can introduce other errors downstream that may have some cryptic side-effects. And unfortunately, in "certain popular dynamic programming languages," they're all too easy to stumble upon. Here's a few tips for avoiding silent failure in Ruby:

Use Hash#fetch over Hash#[]

You want to avoid nil, if at all possible. Nil is the source of many a confusing error. You want your code to fail as quickly as possible, not to pass this "nothingness" around between all sorts of functions... but Ruby doesn't quite make that as simple as it sounds. For an example, let's imagine we get some sort of JSON response from an API, which gets turned into a nice Ruby hash:

employees = parsed_json_response[:employees] { |employee| [employee[:first_name], employee[:last_name]] }

So, that doesn't look too bad. But what happens if our parsedjsonresponse object doesn't contain a key for employees? Suddenly, we're calling the map method on nil. Error! Your first instinct might be to wrap the second expression with an if statement:

employees = parsed_json_response[:employees]

if employees { |employee| [employee[:first_name], employee[:last_name]] }

We can do better!

  employees = parsed_json_response.fetch(:employees) { |employee| [employee[:first_name], employee[:last_name]] }
rescue KeyError => e

In this case, our call to the fetch method will raise a KeyError exception if the employee key is not present in the hash. But we can STILL do better -- that ugly rescue logic gets old quickly, and now that we're avoiding nil, we can replace it with something we actually can use:

employees = parsed_json_response.fetch(:employees, []) { |employee| [employee[:first_name], employee[:last_name]] }

Now, we are able to supply our own default value for the case that the key doesn't exist -- and moving forward, we can always operate under the assumption that our employees are some sort of mappable collection. I've also used this particular construct for refactoring to the Null Object Pattern, but that might be a topic for another post.

Use accessors when possible, not instance variables

Consider the following:

class BlogPost

  def initialize(options)
    @psot_data = DataSource.fetch(options)

  def do_something
    @post_data.inject(0, &:+)

See the typographical error in the initialize method? Instance variables in a class default to nil. If it isn't set, or defined anywhere, and you just throw the asperand in front of any old string, you get nil. This doesn't fail fast enough for my liking. Let's see how we can fix that up:

class BlogPost

  attr_accessor :post_data

  def initialize(options)
    self.psot_data = DataSource.fetch(options)

  def do_something
    post_data.inject(0, &:+)

There. Just a quick caveat: to write to the property, you need to use self in order to invoke the correct method call, as setting a local variable has a higher precedence in Ruby than calling a method. Let's compare the two error messages:

NoMethodError: undefined method `inject' for nil:NilClass


NoMethodError: undefined method `psot_data=' for #<BlogPost:0x007f8cf28bee40>

Much, much clearer with the second example. Now, you probably don't want to expose all of your private attributes of your object to the whole world. I know I don't. So this is one of those cases where you'll have to work it around your design. It IS possible, however, to create private accessors, it just doesn't have the cleanest syntax. Remember, overall, that it's probably better to avoid stateful operations where possible in favor of functional logic - if you find yourself implementing this particular pattern a lot, that may be a code smell of its own.

Avoid postfix rescue clauses

It's handy to be able to return a default value in the event that things fail... but it swallows the real error that you're getting and makes debugging more difficult.

payees = Payroll.employees_to_process rescue []

I've seen these used to good effect before... but remember: compact syntax doesn't necessarily equate to clean code. And if the Payroll class actually raises an error in this case, I would really like to know about it instead of swallowing the error and proceeding as normal. Ruby enables a lot of niceties, but that doesn't make them a good idea. In this case, if I were to try and use this same Payroll class somewhere else, I'd probably want to implement the same rescue logic there. And that, to me, says it belongs somewhere else than where we've got it.

These are just a few examples of how faster-failing code can help us to keep our implementations cleaner and easier to reason about, but there are hundreds of examples in the real world. Just keep your nose open for the smells that can occur: if you find yourself checking for nil a lot, or tying yourself to an external library's errors, refactor to faster failure.

Brian Pratt, Software Craftsman

Brian Pratt is an apprentice cook and a journeyman musician.

Interested in 8th Light's services? Let's talk.

Learn more about our Ruby services

Contact Us