Ryan Davis’s Ruby QuickRef says (without explanation):
Don’t rescue Exception. EVER. or I will stab you.
Why not? What’s the right thing to do?
The exp() function in Ruby returns the value of e^value. It takes value in range [-inf, +inf] and returns the answer in range [0, infinity]. Syntax: Math.exp(value) Parameter: The function takes the value which is to be raised to the power of e. Return Value: The function returns the value of e^value.
Ruby also provides a separate class for an exception that is known as an Exception class which contains different types of methods. The code in which an exception is raised, is enclosed between the begin/end block, so you can use a rescue clause to handle this type of exception.
An exception is an unwanted or unexpected event, which occurs during the execution of a program, i.e. at runtime, that disrupts the normal flow of the program's instructions. In Ruby, descendants of an Exception class are used to interface between raise methods and rescue statements in the begin or end blocks.
TL;DR: Use StandardError
instead for general exception catching. When the original exception is re-raised (e.g. when rescuing to log the exception only), rescuing Exception
is probably okay.
Exception
is the root of Ruby's exception hierarchy, so when you rescue Exception
you rescue from everything, including subclasses such as SyntaxError
, LoadError
, and Interrupt
.
Rescuing Interrupt
prevents the user from using CTRLC to exit the program.
Rescuing SignalException
prevents the program from responding correctly to signals. It will be unkillable except by kill -9
.
Rescuing SyntaxError
means that eval
s that fail will do so silently.
All of these can be shown by running this program, and trying to CTRLC or kill
it:
loop do begin sleep 1 eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure" rescue Exception puts "I refuse to fail or be stopped!" end end
Rescuing from Exception
isn't even the default. Doing
begin # iceberg! rescue # lifeboats end
does not rescue from Exception
, it rescues from StandardError
. You should generally specify something more specific than the default StandardError
, but rescuing from Exception
broadens the scope rather than narrowing it, and can have catastrophic results and make bug-hunting extremely difficult.
If you have a situation where you do want to rescue from StandardError
and you need a variable with the exception, you can use this form:
begin # iceberg! rescue => e # lifeboats end
which is equivalent to:
begin # iceberg! rescue StandardError => e # lifeboats end
One of the few common cases where it’s sane to rescue from Exception
is for logging/reporting purposes, in which case you should immediately re-raise the exception:
begin # iceberg? rescue Exception => e # do some logging raise # not enough lifeboats ;) end
The real rule is: Don't throw away exceptions. The objectivity of the author of your quote is questionable, as evidenced by the fact that it ends with
or I will stab you
Of course, be aware that signals (by default) throw exceptions, and normally long-running processes are terminated through a signal, so catching Exception and not terminating on signal exceptions will make your program very hard to stop. So don't do this:
#! /usr/bin/ruby while true do begin line = STDIN.gets # heavy processing rescue Exception => e puts "caught exception #{e}! ohnoes!" end end
No, really, don't do it. Don't even run that to see if it works.
However, say you have a threaded server and you want all exceptions to not:
thread.abort_on_exception = true
). Then this is perfectly acceptable in your connection handling thread:
begin # do stuff rescue Exception => e myLogger.error("uncaught #{e} exception while handling connection: #{e.message}") myLogger.error("Stack trace: #{backtrace.map {|l| " #{l}\n"}.join}") end
The above works out to a variation of Ruby's default exception handler, with the advantage that it doesn't also kill your program. Rails does this in its request handler.
Signal exceptions are raised in the main thread. Background threads won't get them, so there is no point in trying to catch them there.
This is particularly useful in a production environment, where you do not want your program to simply stop whenever something goes wrong. Then you can take the stack dumps in your logs and add to your code to deal with specific exception further down the call chain and in a more graceful manner.
Note also that there is another Ruby idiom which has much the same effect:
a = do_something rescue "something else"
In this line, if do_something
raises an exception, it is caught by Ruby, thrown away, and a
is assigned "something else"
.
Generally, don't do that, except in special cases where you know you don't need to worry. One example:
debugger rescue nil
The debugger
function is a rather nice way to set a breakpoint in your code, but if running outside a debugger, and Rails, it raises an exception. Now theoretically you shouldn't be leaving debug code lying around in your program (pff! nobody does that!) but you might want to keep it there for a while for some reason, but not continually run your debugger.
Note:
If you've run someone else's program that catches signal exceptions and ignores them, (say the code above) then:
pgrep ruby
, or ps | grep ruby
, look for your offending program's PID, and then run kill -9 <PID>
. If you are working with someone else's program which is, for whatever reason, peppered with these ignore-exception blocks, then putting this at the top of the mainline is one possible cop-out:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
This causes the program to respond to the normal termination signals by immediately terminating, bypassing exception handlers, with no cleanup. So it could cause data loss or similar. Be careful!
If you need to do this:
begin do_something rescue Exception => e critical_cleanup raise end
you can actually do this:
begin do_something ensure critical_cleanup end
In the second case, critical cleanup
will be called every time, whether or not an exception is thrown.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With