I've had a couple of questions recently about how to architecture libraries with respect to exception handling, specifically related to exception granualarity. Questions like:

  • Is is okay to define an exception with a return code inside of it?
  • How many exception classes should I have?

The first place to start is the Error Raising and Handling Guidelines from the Design Guidelines for Class Libraries. There is lots of useful information in the design guidelines even if you aren't writing libraries. The guidelines do mention something like "don't use return codes", but that's about it.

One of the high-value design points when designing exceptions is robustness. The robustness of the customer's code depends directly on the way you structure your exception hierarchy. Here's some code that I've seen. Assume that ApplicationException is the actual type I'm catching, not the base type of a hierarchy:

    // code here...
catch (ApplicationException e)
    if (e.Message.IndexOf("Database Connection Error"))
         // recovery code here

There are at least two problems with the code, one major, and one a bit more subtle. Do you see them?

I'll start with the more subtle one first. If your exception messages are localized, it's likely that the string matching will fail. This is a bad bug, because it only shows up in the localized version in an error case, and your tests aren't very likely to catch it.

The second bug is a whopper. If the exception that comes in isn't a database connection error, it is swallowed by the catch block, never to be heard from again. That's pretty much the worst thing that exception handling code can do.

The problem is that the exception design has forced the user to look inside the exception to decide what to do. This is bad, whether it be the message text, a return code, or the type of an inner exception that the user is looking for. 

To prevent this, I'd recommend the following guideline:

  • Make sure that your user doesn't have to grovel inside of your exception classes to find out whether to handle the exception or not.

One way to determine this is to ask yourself, "Self, would a user ever care about the different cases where ApplicationException is thrown?" In some cases this is obvious, but in other cases, it's not quite so clear. My recommendation is that you err on the side of more granularity rather than less granularity.