Wednesday, August 19, 2009

The first rule of exception handling: do not do exception handling.

Back when I was a VB developer, we used to wrap every single function with an exception handler. We would catch it and write it to a log. More sophisticated versions even featured a stack trace. Yes, really, we were that advanced :) The reason we did that was simple, VB didn’t have structured exception handling and if your application threw an unhandled error it simply crashed. There was no default way of knowing where the exception had taken place.

.NET has structured exception handling, but the VB mindset of wrapping every piece of code in a try-catch block, where the catch catches System.Exception, is still common, I see it again and again in enterprise development teams. Usually it includes some logging framework and looks something like this:

try
{
    // do something
}
catch (Exception exception)
{
    Logger.LogException("Something bad just happened", exception);    
}

Catching System.Exception is the worst possible exception handling strategy. What happens when the application continues executing? It’s probably now in an inconsistent state that will cause extremely hard to debug problems in some unrelated place, or even worse, inconsistent corrupt data that will live on in the database for years to come.

If you re-throw from the catch block the exception will get caught again in the calling method and you get a ton of log messages that don’t really help you at all.

It is much better to simply allow any exceptions to bubble up to the top of the stack and leave a clear message and stack trace in a log and, if possible, some indication that there’s been a problem on a UI.

In fact there are only three places where you should handle exceptions; at the process boundary, when you are sure you can improve the experience of the person debugging your application, and when your software can recover gracefully from an expected exception.

You should always catch exceptions at the process boundary and log them. If the process has a UI, you should also inform the user that there has been an unexpected problem and end the current business process. If you allow the customer to struggle on you will most likely end up with either corrupt data, or a further, much harder to debug, problem further down the line.

Improving the debugging experience is another good reason for exception handling. If you know something is likely to go wrong; a configuration error for example, it’s worth catching a typed error (never System.Exception) and adding some context. You could explain clearly that a configuration section is missing or incorrect and give explicit instructions on how to fix it. Be very careful though, it is very easy to end up sending someone on a wild goose chase if you inadvertently catch an exception you were not expecting.

Recovering gracefully from an expected exception is, of course, another good reason for catching an exception. However, you should also remember that exceptions are quite expensive in terms of performance, and it’s always better to check first rather than relying on an exception to report a condition. Avoid using exceptions as conditionals.

So remember: the first rule of exception handling: do not do exception handling. Or, “when in doubt, leave it out” :)