So I've noticed there seems to be a fair amount of confusion in the Ruby world about exception hierarchies. A number of libraries I use happen to use them in ways that cause problems—usually in ways that surface at the worst possible times near the end of long batch operations. So here's a little primer to refresh your memory and hopefully save some of my sanity if I ever use one of your gems.
Exception is the root class for the whole exception
hierarchy. I see a lot of code that subclasses
for regular non-fatal exceptions. It's not obvious, but this is
really not how it's meant to be used. Imagine you are building a
restful interface. The following code will bring down the
# This is wrong! Inherit from StandardError. class GetSomeRest < Exception; end begin if (7 .. 11).include? Time.now.hour perform_restful_operation else raise GetSomeRest end rescue puts 'You really should not stay up so late.' end
This is because
rescue will only capture errors that
StandardError by default. We should be
StandardError here, as well as for anything
else that's non-fatal. Save
Exception subclasses for very
serious things. Running out of memory should raise an
Exception. When a user presses control-c, it raises
Interrupt, which is descended directly from
Exception rather than
StandardError. Sending a
Unix kill signal to a process does the same thing. If you rescue
Interrupt, then you have to resort
kill -9 to stop your application externally, leaving
it with no chance to clean up after yourself.
Unfortunately, we live in an imperfect world, and we have to deal with libraries that misuse the exception hierarchy. The first thing you should do when you encounter one of these misuses is submit a patch to the offending library. (You could even include a link to this post.) But it's not always possible to get it fixed, so here's the workaround I've been using:
begin perform_possibly_problematic_process rescue Exception => e raise e unless e.is_a? StandardError or e.is_a? GetSomeRest @log.warn e.message # or whatever end
If you're not sure of all the problematic
piece of code could raise, you could just rescue
e if it's an
Interrupt, but this
might swallow some other legitimate serious problems, so it's best
to be specific.
Tell your friends to use Ruby's exception hierarchy as it was intended! Update: Don't feel bad if you didn't know this, apparently even the Lucky Stiff himself has fallen into this trap.