Custom exceptions via metaprogramming |
February 17, 2020 |
Suppose you want a large number of custom exceptions. And suppose you're not afraid of metaprogramming. A few months back, I found myself wanting to be "more specific" with the exceptions I raised in my ongoing RuneBlog project (which is used to create this blog). I wanted to raise specific exceptions with custom parameterized messages (without repeating myself too much).
Here's an example of the "usual" way to do this:
Now there is nothing particularly wrong with that. But as it happens, I wanted a large number of exceptions with fairly descriptive names. I didn't want to type (or even paste) this stuff over and over.
Here comes the "not afraid of metaprogramming" part. This is what I did:
Now, what does this do? The method takes two parameters (or three if you count the target class which defaults to Object). You specify a symbol (for the name of the exception) and a string (for the exception message). It then does two things: It creates an exception class (named after the symbol); and it creates a method of the same name.
Here are examples of calling make_exception:
And here are examples of raising these exceptions:
You might ask: Why define a method returning an exception object? The answer is that I don't want to write these this way:
I perceive four advantages doing it the first way: I don't have to specify the message string each time; I still get to have a descriptive, unique class name without the message making it seem redundantly; I don't have to interpolate values into the message string explicitly; and I don't have to say .new every time.
Some of you may have two questions: Can you (or should you) really have a capitalized method name? Can a method have the same name as a class without confusing the interpreter?
I'll answer both those questions with an example from the Ruby core: There is an Integer class and an Integer method (naturally related to each other). Ruby distinguishes between these by context. It's not impossible, and I argue it's not a bad practice.
As for the parameters, it's intuitive how they work. A % sign signals a numbered replaceable piece of text, and these are replaced with the parameters specified when the method is called.
That part of the code is not really robust, by the way. As it is written now,you could easily confuse it-- make it misbehave or crash. But you get the idea.
So far, I have defined 23 custom exceptions in this project. Each definition occupies a single line. If I want to change the behavior of all of them in some way, I will go and change the make_exception method (which itself is only 9 lines).
Like it, hate it? Comments welcome.