Raising the right exception
Ruby makes it very easy to raise exceptions:
1 2 3 4 |
def finagle_something raise "need a block" unless block_given? ... end |
Using raise
like that (with just the exception message) is really handy for quick-and-dirty solutions. But for Serious Business Stuff™ you may often find the ambiguity of the default RuntimeError a little frustrating.
Ruby has a bunch of predefined exception classes that you can use. For the above, you’d do better to raise an ArgumentError, specifically (raise ArgumentError, "need a block"
). It can pay big dividends to become familiar with the standard exception hierarchy in Ruby.
Sometimes, though, the standard exception classes just aren’t enough. For example, in Capistrano 2.0, I’d like any exception that Capistrano itself raises to be immediately recognizable as a Capistrano exception. The solution?
1 2 3 4 5 |
module Capistrano class Error < RuntimeError; end class ConnectionError < Error; end ... end |
Now, clients of Capistrano only need to look for Capistrano::Error
to safely catch any exceptional conditions within Capistrano:
1 2 3 4 5 |
def use_capistrano ... rescue Capistrano::Error => error warn "couldn't do a capistrano thing: #{error.message}" end |
Furthermore, the benefits of having more specific exception classes (like Capistrano::ConnectionError) are manifold; you can write code to easily detect and retry certain errors, or report some problems differently than others. When you start using specific exception classes, instead of the default RuntimeError, you’ll find you can handle your exceptions much more gracefully, and write much more robust programs.
Reader Comments
Good tip, Jamis. As a seasoned Java programmer, deeply cognizant of the uses and abuses of a rich exception hierarchy, this is an area of Ruby that doesn’t get much coverage. After reading your post, I am all a twitter :)
7 Mar 2007
For those that enjoy spoonfeeding: Ruby’s exception hierarchy: http://www.zenspider.com/Languages/Ruby/QuickRef.html#34 Graph of said hierarchy: http://objectgraph.rubyforge.org/hierarchy_dotOG.html
Great article, Jamis—I know I still make too little use of exceptions in my daily work, and I suspect there are many others out there who underutilize it as well.
7 Mar 2007
I would still like to know what the difference is between an “Exception” and an “Error” in the standard Ruby exception hierarchy. Ruby calls some of its standard exceptions FooException (like SignalException) and some of them FooError (like IOError). Also, there are no best practices I know of about where in the exception hierarchy you should derive your exceptions from. If you derive from RuntimeError, your exceptions will get caught by a simple “rescue”, but is that what you want?
I asked this question on ruby-talk, but was disappointed when no one had anything to say on the topic. I don’t like it when there are corners of the language with no clear rationale or best practices.
7 Mar 2007
After a frustrating time trying to write code to look for a bunch of error codes in XML responses, I realized I should probably be using Exceptions instead.
After making the error codes raise exceptions when they’re parsed out, handling all of the various exceptions suddenly became much easier, and using begin/rescue/else/ensure blocks is fun!
I am all about raising exceptions now, its nice to be able to raise an error at one point in the code, and then handle it higher up the stack.
7 Mar 2007
Ah! I saw you check that change in the other day and was wondering what you were up to. Clever! I’ll have to keep this in mind for future works.
7 Mar 2007
Hi Jamis – interestingly I just recently posted for advice on the Ruby on Rails mailing list re error handling / exceptions recommendations but hadn’t received anything. I’d love to hear your comments/advise re on the below Jamis if you have a few minutes.
On 3/2/07, Greg Hauptmann <greg> wrote: Hi,
Can I ask for some advise re what the best practice is for handling exceptions & errors through my rails code from the point of view of how to display them back to the user? Some items I’d love to be covered include:
[1] is the principle that ALL exceptions (even those the application might not to be able to do anything about) should be caught and then the application then decides what text and view .rhtml? Or should the approach be to allow exceptions to occur and the rails framework to then pass this to a general error page?
[2] errors/exceptions that may occur in the action record / data layer – what categories of errors/exceptions here should be either (a) returned as call didn’t work or (b) exception. That is, from a best practice point of view are there 2 categories of errors/exceptions in the data layer that should be acknowledged and handed differently in terms of how they are captured and passed back to the controller layer? ( e.g. catch exception and process in model code and return appropriate response, OR through to an approach like: any issue that occur in the rails or database itself that get raised as an exception, don’t try to catch them, but let the exception be thrown back to the controller (at which point my first question kicks in re whether the controller should catch these areas and process or let the rails framework through to a general application error page)
[3] where can I see all the possible errors like RoutingError, UnknownAction…? Is it enough to stick with the standard rails rescue_action_in_public method (see below)?
[4] allocation of specific error numbers against each specific error the application catches/can create?
[5] separate log file for error details in a specific parseable/setout format (e.g. for a paging system to reference) in additional to standard log file which contains various other details like rails SQL / timing information, or full exception stack details
[6] an action’s render – this won’t be reached if I through an exception before this point or there is a rails exception I don’t catch before this point no?
[7] anything else related to monitoring application health?
Thanks Greg
8 Mar 2007
Josh: I’ve never read any treatise anywhere describing the difference between the Error and Exception names. Keep in mind, though, that not all Exceptions are Errors. When the user presses ctrl-C, a SignalException will be raised. That’s not an error, it is simply “exceptional behavior”. Most exceptions, though, indicate cases that are most likely errors, like a network connection breaking in the middle of a data transfer, or an invalid password being given. Thus, I’ve started naming most (if not all) of my custom exception subclasses as Errors.
Greg: when it comes to exception handling, you really just need to see what works for you. Capturing every exception can be wearisome, and is usually unnecessary. Catch what you can actually do something about, and let the rest bubble up. You might look into the exception_notification plugin, which will send you an email when an exception bubbles all the way back up to the top.
8 Mar 2007
Hi Jamis,
[1] Do you mind if I ask how do you personally handle the following categories of events.
a) Uncaught exceptions (i.e. bubble up to top) – Usage: System failure / developer can’t do anything – Logging: How? Override rails exception framework to issue a specific “logger.error” message? Check whether a standard Rails “error” will be sent to the log by default anyway… – Alerting: Use of “exception_notification” plugin.
b) Trapped exceptions (i.e. handle explicitly) – Usage: Understood application error (not system error) / Developer can offer value add – Logging: Issue a “logger.error” line – Alerting: How to hook into “exception_notification” plugin from here?
c) Application Specific Audit – Usage: For application specific event (e.g. sale has occurred) and want an email alert and log it too. – Logging: Via use of “logger.info” system (? or need custom solution for audit logging?) – Alerting: How to hook into “exception_notification” plugin from this point?
[2] Do you think a best architecture would be that the logging framework itself should cater for email &/or sms’ing of key log events (e.g. criticals). Then if one was to customise the exception frame to automatically log/alert via the logging framework (i.e. exception framework issues a log message, the logging framework then determines when/how to send the alert?)
Cheers Greg
21 Mar 2007