my favorite exception types

Saturday, 21 April 2012

Microsoft’s .Net Framework defines literally hundreds of exception types, and application developers can extend that list ad infinitum. But just a few have emerged in my experience as especially useful in clarifying my thinking and my code. These types fall into three broad categories: Precondition Violations, Contained Failures, and System Failures.

Precondition Violations

Nat Pryce dryly suggested a type from which these might ideally derive – “StupidProgrammerException”. They’re thrown when it’s clear that a software developer has done something wrong. Documenting these exceptions allows proactive developers to avoid causing them at all, and throwing them with descriptive messages allows experiment-oriented developers to grok the API’s semantics quickly.

The .Net Framework provides several handy exception types for reporting precondition violations:

  • NotImplementedException indicates that the attempted operation is not supported by the current type. For instance, a partial interface implementation could provide real functionality for some methods and throw NotImplementedException from others. (Think ReadOnlyCollection.Add.)
  • NotSupportedException indicates that the attempted operation is not valid on the current object. For instance, a read-only FileStream throws NotSupportedException if a client attempts to write.
  • InvalidOperationException indicates that the attempted operation is not allowed due to the current state of the object (or the system it’s abstracting). For instance, attempting to execute a SqlCommand on a closed connection results in an InvalidOperationException, as might attempting to draw funds from an empty account.
    Concession: Calling this a ‘stupid programmer exception’ is an over-generalization. Some invalid operations aren’t easily preventable at compile time and are best treated as contained failures, as discussed below.
  • ArgumentException indicates that there is a problem with one or more parameter values passed to the attempted operation. But almost any error can be arguably attributed to some argument somewhere, so ask yourself, “Can this violation be detected by inspecting the arguments themselves – either individually, or in combination?” If so, use ArgumentException (or a subtype). If not – if the violation can be detected only by comparing the arguments against the state of the target component, throw InvalidOperationException instead.
In general, these precondition violations should not occur in production code, so catching and handling them is rarely necessary. When an ArgumentException shows up in application logs, it’s time to plan a bug fix.

Contained Failures

Not all exceptions are preventable – even when we programmers manage to transcend our stupidity. It’s useful to divide the remaining failure modes into two categories – those that are specific to a particular operation or entity (which I’m calling contained failures), and those that likely affect whole swaths of functionality (system failures). Many, though not all, contained failures are directly attributable to user behavior.

I’ve collected a useful set of these exception types over the years.

  • InvalidInputException indicates that a user’s input is flawed in some way. It is analogous to ArgumentException, but at a higher level of abstraction – targeting end users rather than software developers. It may report more than one error in a single instance, and should carry a localized message where applicable. Examples include letters being entered in a numeric field, or a required field being left blank.
  • InvalidOperationException, as discussed above, indicates that the attempted operation is not allowed due to the current state of the object (or the system it’s abstracting). It’s often preferable to provide the client a means of checking this state before making a call, but sometimes it’s most efficient to allow an attempt and then report any failure. When InvalidOperationException is used this way, the user-friendliness concerns described under InvalidOperationException may apply here as well.
  • AccessDeniedException indicates that the current user does not have permission to perform the requested operation. Usually some form of audit should be written when this exception is thrown or caught.
  • ItemNotFoundException indicates that a request affecting a single, specific object (usually by some unique identifier) failed to locate the object. The exception’s message should clearly state the criteria of the query that came up empty.
  • DuplicateItemException indicates that a request to create a new object failed because the object (or some part of it that ought to be unique) already exists. This exception’s message should clearly communicate the points of duplication.
  • ConcurrentUpdateException is thrown when two or more mutually exclusive requests were received simultaneously. Typically, one request will be honored, while the other(s) will fail with this exception. ConcurrentUpdateException is closely linked to the optimistic locking pattern.
  • ResourceInvalidException indicates that problems have been encountered in data coming into the system from a repository or feed. The problem could be caused by flawed persistence or translation logic, or introduced be manipulating data manually. An example would be records in a relational database that have no valid equivalent when mapped into an object model. The key here is that the problem appears to be isolated to a particular entity.

System Failures

When an exception appears to render an entire system or component unusable (I say ‘appears’ because logically it’s hard to prove a negative – especially at compile time), it’s useful to throw an exception that indicates this. Documenting and throwing these exceptions is the first step to getting them the attention they’ll deserve at run time.
  • ConfigurationException (or ConfigurationErrorsException) is the .Net Framework’s way of telling us when system settings (usually global) are unusable. These errors usually occur immediately after deployment, and require engineering intervention for correction.
  • ResourceUnavailableException, another custom type that I use often, indicates that a some vital dependency of an application or component is non-operational or cannot be reached. Examples might include a remote server that won’t respond, or a discrete subsystem throwing unexpected exceptions.

Of course, all this is just the beginning. It’s one thing to throw the right exceptions; it’s another to catch and handle them effectively. And isn’t that the real point? The goal of throwing the right exception is to allow the application to contain the damage and enable the solution. But that’s a topic for another day.

What exception types do you find the most useful?