Why Is It Bad Style to 'Rescue Exception =≫ E' in Ruby

Why is it bad style to `rescue Exception = e` in Ruby?

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 evals 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

What type of exception does rescue = e captures?

If you don't specify an exception filter, the implicit default is StandardError (scroll down to the bulleted list of default exceptions, it lists the defaults for both rescue and raise). This also applies to the trailing "inline" rescue modifier, whose syntax doesn't allow to specify an exception filter at all:

foo rescue bar
# always implicitly rescues StandardError

Not all Exceptions inherit from StandardError, so this will not rescue all exceptions, but it will rescue all that can be sensibly rescued. Exceptions that don't inherit from StandardError are usually ones that are so severe or that point to a problem inside the execution engine, that user code cannot recover from them in a sensible way. For example, an out-of-memory-error or an internal error inside the VM.

SystemStackError is an example of an exception that doesn't really make sense to handle in user code. If the stack is exhausted, you cannot make any more method calls, but it is pretty much impossible to do anything in Ruby at all without making a method call. Plus, a stack overflow is usually a programming error, you need to fix the error, not the symptom.

Note that such a "catch-all" exception handling is usually frowned upon. You should be as narrow as possible in your exception specification and maybe even examine the exception object inside your rescue block to make sure that the exception you got is really the one you want to handle. Very broad catch-all exception specifications have the risk that you will rescue exceptions that you don't know how to handle.

What does the = in rescue Exception = e do?

The Hash Rocket is a Syntactic Token

The hash rocket is actually a syntactic token. You can find the token in the grammar defined by ext/ripper/ripper.y:

%token tASSOC           /* => */

In other words, Ripper uses the hash rocket to associate things.

How tASSOC is Used

In general, this token is used in hash literals to associate a key with a value. For example:

{ :e => 'foo' }

associates the string literal foo with the symbol :e. This common usage is why people tend to think of the hash rocket as solely a hash-related construct.

On the other hand, the following associates a variable with an exception:

rescue => e

In this case, rather than associating a key with a value, Ripper is associating the variable e with the implied StandardError exception, and uses the variable to store the value of Exception#message.

Further Reading

If you understand tokenizers, lexers, and parsers, ripper.y and the various contents of ext/ripper/lib/ripper are instructive. However, on page 19 of Ruby Under a Microscope, Pat Shaughnessy warns:

Ruby doesn’t use the Lex tokenization tool, which C programmers commonly use in conjunction with a parser generator like Yacc or Bison. Instead, the Ruby core wrote the Ruby tokenization code by hand.

Just something to keep in mind when you're trying to grok Ruby's grammar at the source code level.

ruby when/how to use rescue/ensure?

In general exceptions should be used for exceptional events. Not regular application flow.

When catching exceptions always be specific and only rescue exceptions that you know what to do with.

begin 
pats "hello world"
rescue
nil
end

This example shows a serious flaw in the code in yout question - you created a black hole that swallows the NoMethodError that would have told us that there is a typo in the code. This makes debugging extremely difficult. This anti-pattern is known as Pokémon Exception Handling (Gotta catch em' all).

ensure just ensures that the code is run no matter if the code raised an exception or not. Its used to for example guarantee that the method closes a file handler that it has opened or rolls back a transaction. Its a really big hammer that should be used very sparingly.

HTTParty does not actually raise exceptions when the response code is a "error" code - because its not an exceptional event. Its a part of normal application flow when dealing with HTTP requests. Shit happens. HTTParty raises exceptions if you can't reach the server at all or you can't even reach the network. Those are exceptional events.

class GeolocationClient
include HTTParty
base_uri "https://www.googleapis.com/geolocation/v1"
format :json
attr_accessor :options

def initialize(api_key:)
@options = {
api_key: api_key
}
end

def geolocate
begin
response = self.class.get("/geolocate", options)
if response.successful?
response
else
logger.info("Geolocation API call was unsuccessful. Status code: #{response.code}")
handle_unsuccessful_request
end
rescue HTTParty::Error => e
logger.warn(e.message)
handle_unsuccessful_request
end
end

private

def handle_unsuccessful_request
{ "message" : "unable to find location" }
end
end

response.successful? tests if the response is in the 2xx "happy range". Use a switch statement if you want handle each code or range of codes separately.

ruby how to stop the execution after rescue

If by 'not continue' you mean that you want the original error to continue (i.e., you just want to take action on the way by), you can call raise inside the rescue block, which re-raises the original error.

def foo
begin
# stuff
rescue StandardError => e
# handle error
raise
end
end

You can also simply return from within the rescue block as well.

def foo
begin
# stuff
rescue StandardError => e
# handle error
return some_value
end
end

As an aside, generally you want to rescue StandardError rather than Exception. All the things that you can reasonably handle within your application are covered under the StandardError. The things outside that are things like out-of-memory, etc., that are outside your control.

Can you rescue from specific errors with messages in ruby?

See https://stackoverflow.com/a/23771227/2981429

If you call raise inside a rescue block, the last raised exception will be re-raised.

In your exception block, you can check the message and choose to re-raise or not:

begin
User.new.charge
rescue RuntimeError => e
case e.message
when "This is a runtime error"
# put your handler code here
else
raise # re-raise the last exception
end
end

However if it's your goal to solely rescue errors that you yourself raise manually, then it's probably easier to define a custom error class instead:

class MyError < StandardError; end

Then instead of raise RuntimeError.new("message") use raise MyError.new("message"), and rescue it normally:

begin
User.new.charge
rescue MyError => e
# handler
end

This way you don't have to worry about your rescues interfering with the built-in exceptions.

Alternative to rescue Exception

You can rescue for StandardError, or simply rescue, which are the same:

rescue StandardError => e
# or
rescue => e

You can see in the following table which exceptions are rescued from StandardError - Note that they are a subset from Exception, and conceitually should be errors that are OK to be catch.

Of course you can have gems that defines exception in the wrong place, but this should not happen in well-developed gems.

ruby exceptions

(source: rubylearning.com)

I personally like to rescue only exceptions I know how to handle, except when it is to add in a log/backtrace system to consult the errors later. If this is the case, I usually rescue StandardError

How to rescue from a specific error (Ruby on Rails)

You can catch different errors and do the same action on them or do different actions. The syntax is the following.

Imagine you want to do different actions for different errors:

begin
# Do something
rescue JSON::ParseError
# Do something when the error is ParseError
rescue JSON::NestingError, JSON::UnparserError
# Do something when the error is NestingError or UnparserError
rescue JSON::JSONError => e
# Do something when the error is JSONError
# Use the error from this variable `e'
rescue # same as rescue StandardError
# Do something on other errors
end

If you are going to place all the code in the function inside a begin rescue end block, then you can ommit the begin end words, so instead of writing:

def my_func
begin
# do someting
rescue JSON::ParseError
# handle error
end
end

You may write

def my_func
# do something
rescue JSON::ParseError
# handle error
end

Remember to never rescue from Exception. I know my answer may be a little too broad for your question but I hope it helps you and other people in similar doubts.



Related Topics



Leave a reply



Submit