I recently read Rebecca J. Wirfs-Brock's excellent article, “Toward Exception Handling Best Practices and Patterns” (pdf), in the September/October issue of IEEE software. The article contained lots of good advice on how to properly use exceptions, but I thought it was most useful where it touched on what NOT to do with exceptions.
So, without further ado, here are some of my favorite ways to abuse exceptions and make your code truly painful to maintain.
1 2 3 4 5
Exception Swallowing is the practice of catching base exceptions and doing nothing with them.
The VB.Net example above is evil because it captures any exception that occurs, then discards the exception leaving no record that the exception was ever raised. This leads to an incredibly difficult to debug situation where a program behaves oddly or incorrectly but there is no sign that anything went wrong.
For a particularly insidious trick, try combining Exception Swallowing with overly long code blocks.
Repeatedly Catching, Logging, and Rethrowing the Same Exception
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
The first rule to working with exceptions is to “handle exceptions as close to where they are thrown as possible”. Unfortunately, people sometimes get confused and forget the “as possible” part of this maxim. This leads to well meaning but misguided programmers catching and rethrowing the same exception all the way up the call stack as in the C# code above. This hides the root cause of the exception, as each thrown exception truncates the call stack to its level.
Logging the exception compounds the problem. If the exception is caught and rethrown at four different levels than three of the log entries are extraneous. This greatly decreases the signal to noise ratio in your log files and makes finding the root cause of the problem more difficult.
Don’t catch exceptions that you can’t handle. Instead let them bubble up until they can be handled. If an exception is truly unrecoverable, then let it bubble to the top of the call stack, but take care to log it before exiting the application. That way all exceptions are guaranteed to be logged. Furthermore, if you later want to change how you log exceptions there is only one place to make the change.
Note: Sometimes it is appropriate to catch and rethrow an exception. One such example is to maintain the proper level of abstraction. For instance, a Model class in an MVC program shouldn’t throw a low level SQLException if a record it expects to exist can’t be found. Instead it should catch the low level exception and throw an exception like NoSuchElementException that is more appropriate to the level of abstraction. However, you must always remember to chain the exceptions so as not to lose the low level, root cause of the error.
Using Exceptions for Program Flow control
1 2 3 4 5 6 7 8 9 10
Exceptions are to be used for exceptional situations. If an event is expected than it is not exceptional. There are several problems with the above Java example.
- Using exceptions to control program flow produce a sudden jump of execution control that makes tracing code difficult. This is similar to using goto (famously considered harmful by Dijkstra).
- Raising an exception is a computationally expensive event. Depending on the language and implementation it can involve freezing all executing threads and unwinding all the way to the top of the call stack.
The above code could be better written as:
1 2 3 4 5 6
What You Should Do
There is plenty of good advice on the net about how to properly use exceptions. The Portland Pattern Repository has an excellent Exception Patterns Wiki. Joshua Bloch has some excellent advice on handling exceptions in Effective Java
Good exception handling is important, but it isn’t hard. If you follow some basic common sense and avoid the pitfalls discussed above you’ll be in really good shape.