Did you ever ask yourself, how you should “design” your exceptions? Ever wondered if you understood the idea behind exceptions? Do you know, what you should use exceptions for? This article aims to clarify how and when (not) to use exceptions.
I will use some Ruby for the example code, but the principles are the same in all other programming languages (at least the ones I know).
Exception as in exceptional
Exceptions should be used for situation where a certain method or function could not execute normally. For example, when it encounters broken input or when a resource (e.g. a file) is unavailable. Use exceptions to signal the caller that you faced an error which you are unwilling or unable to handle. The exception is then passed to the caller who has the chance to either handle the exception or pass it on (chain-of-responsibility).
1 2 3 4 5 6 7 8 |
|
Don’t use exceptions for your normal application flow. Take a look at the following example:
1 2 3 4 5 6 7 8 9 |
|
In this example we use an exception to signal the caller that the client disconnected by sending a quit
command. Something that will happen in every normal use case of this method. There are multiple reasons why you should not do this:
Exceptions are not designed for this. Developers using this API or reading through the source code will be confused. It looks like a failure scenario but it’s just some sort of flow control.
Exceptions are hard to follow. If you are using exceptions like this, you are using just another form of a
goto
statement. I don’t have to clarify why usinggoto
is a bad idea, do I?Exceptional exceptions? If you use exceptions for normal situations, how do you signal unusual situations?
Exceptions are slow. Because exception only rarely occur, performance is not a priority for the implementers of compilers nor the designers of the language. Throwing and catching exceptions is inefficient and many times slower than a simple check of a return value or a state field.
Don’t use exceptions to signal something completely normal. Don’t use exceptions to control your normal application flow. Use return values or state fields for flow control instead.
It’s all about the hierarchy
Another question that pops up frequently: What structure (hierarchy, inheritance, …) should I use for my own exceptions?
Exceptions should be as specific as possible. Especially if you are designing an API that will be used by third-party developers.
Your own exceptions should all inherit from a common exception in your namespace. And this exception class in turn should inherit from the standard library exception (e.g. StandardError
in Ruby, Exception
in Java).
1 2 3 4 5 6 7 |
|
This allows the caller to decide which exceptions he would like to handle. If you don’t follow this advice, you might end up with an error design as broken as the one in Ruby’s Net::HTTP. Because there is no common error class, a caller willing to catch all module specific exceptions must list all possible exceptions that may occur:
1 2 3 4 5 |
|
Be verbose
When throwing an exception, you should supply an error message describing what exactly went wrong. This is especially useful for logging and debugging.
1
|
|
The error messages are only intended for humans. They should not be used by the calling method to determine what probably went wrong.
tl;dr
Exceptions should be used to signal serious errors from which your method is unable or unwilling to recover. In a best case scenario, no exceptions should occur during the whole application flow.
Be sure to structure your exceptions and use a hierarchy to simplify exception handling for the caller.
Use the optional error message to supply additional debug information.