Using the Right Exception Subclass in Ruby

Using the right exception subclass in ruby

"It depends".

One of the main problems with Ruby is the lack of good specification. It used to be worse, though.

This is mainly a question of style: If your error can be described well with one of the builtin exception classes, use it. If you think a subclass makes sense, use it.

Otherwise, you could consider to follow this lead from a C++ coding standard:

Creating very elaborate exception
hierarchies is a waste of time. Nobody
ends of caring and all the effort goes
to waste. Instead, create one
exception per library or namespace and
have an exception reason within that
exception to indicate the type of the
exception.

For example, for your OS encapsulation
libary, make an exception called
OsencapException.

Ruby exception inheritance with dynamically generated classes

Ok, I'll try to help here :

First a module is not a class, it allows you to mix behaviour in a class. second see the following example :

module A
module B
module Error
def foobar
puts "foo"
end
end
end
end

class StandardError
include A::B::Error
end

StandardError.new.kind_of?(A::B::Error)
StandardError.new.kind_of?(A::B)
StandardError.included_modules #=> [A::B::Error,Kernel]

kind_of? tells you that yes, Error does possess All of A::B::Error behaviour (which is normal since it includes A::B::Error) however it does not include all the behaviour from A::B and therefore is not of the A::B kind. (duck typing)

Now there is a very good chance that ruby-aws reopens one of the superclass of NameError and includes Amazon::AWS:Error in there. (monkey patching)

You can find out programatically where the module is included in the hierarchy with the following :

class Class
def has_module?(module_ref)
if self.included_modules.include?(module_ref) and not self.superclass.included_modules.include?(module_ref)
puts self.name+" has module "+ module_ref.name
else
self.superclass.nil? ? false : self.superclass.has_module?(module_ref)
end
end
end
StandardError.has_module?(A::B::Error)
NameError.has_module?(A::B::Error)

Regarding your second question I can't see anything better than

begin 
#do AWS error prone stuff
rescue Exception => e
if Amazon::AWS::Error.constants.include?(e.class.name)
#awsError
else
whatever
end
end

(edit -- above code doesn't work as is : name includes module prefix which is not the case of the constants arrays. You should definitely contact the lib maintainer the AWSError class looks more like a factory class to me :/ )

I don't have ruby-aws here and the caliban site is blocked by the company's firewall so I can't test much further.

Regarding the include : that might be the thing doing the monkey patching on the StandardError hierarchy. I am not sure anymore but most likely doing it at the root of a file outside every context is including the module on Object or on the Object metaclass. (this is what would happen in IRB, where the default context is Object, not sure about in a file)

from the pickaxe on modules :

A couple of points about the include statement before we go on. First, it has nothing to do with files. C programmers use a preprocessor directive called #include to insert the contents of one file into another during compilation. The Ruby include statement simply makes a reference to a named module. If that module is in a separate file, you must use require to drag that file in before using include.

(edit -- I can't seem to be able to comment using this browser :/ yay for locked in platforms)

Best practice: Using system supplied or custom exceptions for error conditions in ruby?

I believe the best practice is to raise your own custom error, namespaced in a module. All of your specific exception classes should inherit from one namespaced exception that inherits from StandardError. So for your case:

module MyApp
class Error < StandardError; end
class ArgumentError < Error; end
end

and raise MyApp::ArgumentError when the user provides bad arguments. That way it differentiates from an argument error in your code. And you can rescue any uncaught exception from your app at a high level with MyApp::Error.

You should also check out Thor. It can handle most of the user argument stuff for you. You can look at Bundler for a good cli usage example.

Ruby's Exception Error classes

Define an initialize method, which takes the message as an argument with a default value. Then call StandardError's initialize method with that message (using super).

class MyError < StandardError
def initialize(msg = "You've triggered a MyError")
super(msg)
end
end

Handle default exception in ruby

First of all, you really shouldn't subclass Exception. It is the superclass of all Ruby exceptions, including NoMemoryError, SyntaxError, Interrupt, SystemExit; all of which you don't normally need to rescue from. Doing so, whether accidentally or on purpose, is discouraged since it can prevent a program from exiting properly, even if it was interrupted by the user. It can also hide or produce some quite obscure bugs.

What you want to subclass is StandardError, which is the superclass of most Ruby errors we see in day-to-day programming. This class is also the one which will be rescued should you not specify one:

begin
object.do_something!
rescue => error # will rescue StandardError and all subclasses
$stderr.puts error.message
end

I believe this is the "default behavior" you are looking for. You can handle a specific error, then all other errors in general:

class CustomApplicationError < StandardError
end

begin
object.do_something!
rescue CustomApplicationError => error
recover_from error
rescue => error
log.error error.message
raise
end

The else clause is not meaningless in error handling. It will execute the nested code if and only if no exceptions were raised, as opposed to the ensure clause which will execute code regardless. It allows you to handle success cases.

begin
object.do_something!
rescue => error
log.error error.message
else
log.info 'Everything went smoothly'
end

Ruby custom error classes: inheritance of the message attribute

raise already sets the message so you don't have to pass it to the constructor:

class MyCustomError < StandardError
attr_reader :object

def initialize(object)
@object = object
end
end

begin
raise MyCustomError.new("an object"), "a message"
rescue MyCustomError => e
puts e.message # => "a message"
puts e.object # => "an object"
end

I've replaced rescue Exception with rescue MyCustomError, see Why is it a bad style to `rescue Exception => e` in Ruby?.

Instance variable access in subclasses

You need to call the super initializer in the initializer of the subclass.

class AddSubNode < Node
def initialize op, l, r
super()
@op = op
@l = l
@r = r
end
...

edit: forgot parenthesis

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


Related Topics



Leave a reply



Submit