26
JUN2008
Summoning Error Classes As Needed
In the past, I've written a lot of code like this:
class SomeThing
class SomeError < RuntimeError; end
class AnotherError < RuntimeError; end
class YetAnotherError < RuntimeError; end
# some methods that can raise the above errors...
end
I have a new strategy I've been using for code like this and it has really been working out well. Here is how I do the same thing today:
class SmarterThing
def self.const_missing(error_name) # :nodoc:
if error_name.to_s =~ /Error\z/
const_set(error_name, Class.new(RuntimeError))
else
super
end
end
# error raising methods here...
end
Let's discuss how this works. The const_missing()
method is a hook in Ruby, much like the beloved method_missing()
. Note that const_missing()
is a class method though, instead of an instance method. When a constant is used and Ruby can't find it, a call to the hook will be triggered.
In this version, I just check to see if the constant name ends in Error
. If it does, I summon an Exception
subclass on the fly. Other calls to this hook are forwarded on to Ruby's default error raising implementation via super
.
The building of the Exception
subclass has a few interesting points of note. First, we see that we can build a Class
object as we do any other with a simple call to new()
. Beyond that, we can pass new()
a parent Class we would like to inherit from. So if you would prefer to inherit from StandardError
, you can just change the reference here. Finally, const_set()
assigns the new Class
to the constant name referenced and returns it. This means that future references for the same constant will not go through this hook and will receive the same Class
.
That's the how, but let's talk a little about the why.
When I showed this trick to a friend, he complained that these summoned errors are not easily RDoced. That's true, but I've actually found this is improving my documentation instead of hurting it.
Raise your hand if you tend to click on all the errors listed in the API documentation and read about those. Yeah, I don't either. With those gone (and note that I explicitly disabled RDoc for my hack), I just add details about the Exception
s a method can raise to the RDoc of that method. The end result of all this is that the documentation has moved to a place where it is helpful to me and thus I actually read it.
A final bonus of this technique is that it even works if you are dynamically generating error names in some code, say with const_get()
.
Comments (5)
-
Adam Keys June 26th, 2008 Reply Link
A trick that brings my favorite method,
Class.new
, together with my favorite family of methods,const_*
? Good show! -
I like the implementation as a concept, but I am concerned about what the results would be for client code. This would seem to encourage the creation of an arbitrarily large number of Error classes. To my mind the goal of raising an error is to have some other part of the program catch it and do something intelligent to fix it. I, therefore, only create a new error class when I think the error can be handled somewhere. This being the case, I find a list of all potentially handleable errors useful, even if I don't click on them in the rdocs.
I guess my question is, why not just raise a
RuntimeError
or some other already defined Error class and use the properties of the Error class to give more detail?-
StarTrader—
const_set
means that the newly generated Error classes won't remain anonymous; you'd be able to rescue them by name. -
This blog post from Jamis Buck may interest you:
http://weblog.jamisbuck.org/2007/3/7/raising-the-right-exception
-
-
Also note that typo's won't be caught, causing more subclasses of
RuntimeError
to be created.Example:
def foo # ... some code ... raise InvalidGidgetError end def bar # ... raise InvlaidGidgetError end
now you have two exceptions.
Maybe something like this would be more suitable, although not as concise.
def create_empty_exceptions(*args) # or whatever else you'd like to call it args.each do |exception_class| # same code as your example to create classes and a set them to a constant end end