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 rescue
d 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 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
Related Topics
What Does "File.Sync = True" Do
Can You Have Multiple Versions of a Gem in a Gemfile
Quote All Fields in CSV Output
Split Float into Integer and Decimals in Ruby
How to Pass Multi Value Query Params in Swagger
Accessing a Ruby Hash with a Variable as the Key
Gmaps4Rails:Setting Map Width and Height
How to Cancel Evaluating a Required Ruby File? (A.K.A. Top-Level Return)
What Is an Eoferror in Ruby File I/O
Defined' and 'Unless' Not Working as Expected
Why Can't We Override '||' and '&&'
How to Create Alias to Attributes in Ruby
Why Does Ruby Use Its Own Syntax for Safe Navigation Operator
Stubbing Controller Actions in Rspec Request Specs
Rails 404 Error for Stylesheet or JavaScript Files
Rails Syntax Error: Unexpected Keyword_Ensure, Expecting Keyword_End