December 07, 2008

Exceptions

Before Object-Oriented programming (OOP), error conditions were commonly reported via a return code, an OS signal, or even by setting a global variable. One of the most useful notions introduced by OOP is that of an 'exception' because they drastically reduce the mental load of handling error cases.

In the old style of error handling, any time a function was called which could result in an error, the programmer had to remember to check whether an error occurred. If he forgot, the program would probably fail in some mysterious way at some point later down the line. There was no mechanism built into the language to aid the programmer in managing all the possible error conditions. This often meant that error handling was forgotten.

The situation is much improved with Exceptions, primarily because they offer a fool-proof way to ensure that error handling code is invoked (even if it is code for reporting an unhandled exception). This both makes it unnecessary to remember to check for errors, and it increases the cohesion of such code (i.e. it can be gathered into 'catch' blocks instead of mixed in with the logic of the function). Both of these help preserve the Unit Economy of the author and reader of the code.

Unfortunately, despite being such a useful innovation, exceptions are often abused. We've all seen situations where one must catch three different exceptions and do the same thing for each. We've all seen situations where only a single exception is thrown no matter what goes wrong, and it doesn't tell us anything about the problem. Both ends of the spectrum reflect a failure to use exceptions with the end user of the code in mind.

When throwing an exception, one should always keep two questions in mind: "Who is going to catch this?" and "What will they want to do with it?". With this in mind, here are a number of best practices I've seen:

Each library should have a superclass for its exceptions.

Very frequently, users of a library aren't going to be interested in what specific problem occured within the library; all they're going to want to know is that the library either did or didn't do its job. In the latter case, they will want the process of error handling to be as simple as possible. Having all exceptions in the library inherit from the same superclass makes that much easier.

Create a new subclass for each distinct outcome.

Most often, exception subclasses are created for each distinct problem which can arise. This makes a lot of sense to the author, but it usually doesn't match what the user of the code needs. Instead of creating an exception subclass for each problem, create one for each possible solution. This may mean having exceptions to represent: permanent errors, temporary errors, errors in input, etc. Try to consider what possible users of the component will want to do with the exception, not what the problem originally was.

Remember that exceptions can hold data.

In most languages, exceptions are full-fledged classes, and your subclasses can extend them like any other parent class. This means that you can add your own data to them. Whether it is an error code for the specific problem, the name of the resource which was missing, or a localization key for the error message, including specific data in the exception object itself often is an invaluable means for communicating data which would otherwise be inaccessible from the 'catch' block where the exception is handled.

What other tips and tricks would you suggest?

5 comments:

  1. Exceptions should also be self-describing and logged, using whatever mechanism your language supports (toString(), ex.what(), etc.). It's frustrating to see an exception thrown in a production environment which is hard to produce but gives you nothing more than some very basic information, like the exception class name.

    Any exception should give as much information as possible. And, since you noted exceptions can hold data, this should usually be possible.

    ReplyDelete
  2. In my opinion exceptions should not be used to pass information except that which is necessary to identify the immediate cause of the exception, e.g., stack trace plus whatever variables had an invalid value etc. Exceptions should not be seen as a pipe for passing extra information, because if that information really *needs* to travel to some destination there should have been an explicit code path for it to travel. One serious potential side effect of extra info in an exception is that if the exception is NOT handled the way you expect, the info in the exception may be exposed to the user, which may be a security risk or privacy issue.

    ReplyDelete
  3. In my experience, programmers tend to rely on exceptions to alleviate themselves of the responsibility of writing robust code. For example, a square root function which doesn't bother to check if the input it was passed is a negative number.

    Ideally, programmers should anticipate all the various ways in which their code can be misused, and handle them as gracefully as possible. Doing so can add more functionality in the code and provide the user with a more productive experience.

    ReplyDelete
  4. I agree with Tom that having language-level support for exceptions is not a substitute for writing rigorous, defensive code. On the contrary, I think using exceptions (i.e., throwing and catching them appropriately) is a major boon to writing robust and correct code. Once a programmer has "anticipate[d] all the various ways in which their code can be misused", the most graceful thing to do is raise a well-choosen exception to allow a component with more context of the actual user intent to gracefully clean up and deal with the problem in a friendly way.

    ReplyDelete
  5. Oops... I meant: "...the most graceful thing to do is *often to* raise a..."

    ReplyDelete