This post was originally published on the New Bamboo blog, before New Bamboo joined thoughtbot in London.
We are currently hiring at New Bamboo. As part of the process, we send candidates a code test to complete at their own leisure. I won’t reveal any details here, but it’s nothing special.
Nevertheless, I have personally reviewed a bunch of these code tests, and
there’s something that has caught my attention. A number of applicants
misunderstand the usage of the inline rescue
construct in Ruby. Not a whole
lot of them, but enough that I felt compelled to write this piece in order to
help other people out there.
What I am seeing is code like the following:
do_something rescue SomeException
I assume applicants read that as “if SomeException
is raised, rescue it and
just carry on as if nothing had happened”. Unfortunately, this is not the case.
I’ll show with a different example. See this:
do_something rescue nil
What the above does is:
- It calls the method
do_something
- If that raised an exception, it’s rescued immediately. The line evaluates to
nil
- If no exception was raised, the line evaluates to the return value of
do_something
The following is equivalent:
begin
do_something
rescue
nil
end
Five lines with their indentation against a clever one-liner. Tempting! If you
know that do_something
may raise an exception, you use an inline rescue
to
quickly stop that and continue. Isn’t that neat?
Except that it’s a headache waiting to happen. Consider this code:
def someting_is_wrong?
rand < 0.01 # Good 99% of the time
end
def do_something
if something_is_wrong?
raise MyIgnorableException
else
"foo"
end
end
begin
do_something
rescue
nil
end
This code looks ok. However, when we run it, something is amiss. It always
returns nil
. We expected it to fail (return nil
) only in 1% percent of the
cases, but it’s actually failing 100% of the time. What’s going on?
Well, that we misspelled the name of the first method, that’s what. Read again:
someting_is_wrong?
. An “h” is missing.
We are calling a method that doesn’t exist. This raises a NoMethodError
, which
bubbles up to the rescue
, which in turn ends up rescuing more than we wanted.
Remember that an unqualified rescue
will catch any exceptions that inherit
from StandardError
. That is dangerous, as it is likely that there will be
other problems.
Instead we should use the following invocation:
begin
do_something
rescue MyIgnorableException
nil
end
This time we get a NoMethodError
straight away. We notice the problem, fix it,
and continue on our merry way, safe in the knowledge that we’ll be notified of
any unforeseen problems.
In summary, there are two problems with inline rescue
of exceptions:
- The syntax can be misleading, making you think that it’s rescuing only a
specific type of exception, when it’s not. It’s rescuing any type of exception
(as long as it inherits from
StandardError
), then evaluating the expression that follows it - Rescuing exceptions without sensible filtering will make you miss other problems that you didn’t expect
Therefore, here are two pieces of advice:
- Never use the unfiltered version of
rescue
- Never use the inline
rescue
, as it’s effectively an unfilteredrescue
OK, I say “never”, but Avdi Grimm could come up with a decent use case for the
inline rescue
. Rather than outlining it here, stealing Avdi’s thunder, and
making this longer than it needs to be, I’m just going to link to the screencast
where he explains it better than I can, in just 3:17 minutes. It’s issue #22 of
his excellent Ruby Tapas, and it’s available for free at his blog: Ruby Tapas
#22 - Inline Rescue.
Happy hacking!