Eric Lippert has a great blog post where he divides exceptions into four categories. I find these categories a useful way to think about exception handling and designing exception hierarchies. All examples and subsequent discussion is in the context of C#. The four categories in Lippert’s schema are:
- fatal exceptions “are not your fault, you cannot prevent them, and you cannot sensibly clean up from them.” Examples include
- boneheaded exceptions “are your own darn fault, you could have prevented them and therefore they are bugs in your code.” Examples are
ArgumentExceptionand its subtypes,
- vexing exceptions “are the result of unfortunate design decisions. Vexing exceptions are thrown in a completely non-exceptional circumstance, and therefore must be caught and handled all the time.” An example of a method that throws vexing exceptions is
Int32.Parse, which throws an exception if the input cannot be parsed into an
- exogeneous exceptions “are the result of untidy external realities impinging upon your beautiful, crisp program logic.” Most network and file exceptions are exogeneous exceptions.
A fatal exception means that something is fundamentally wrong with the execution environment of the current thread, or of the whole program. That something means there’s no hope that it can continue executing and do what it was meant to, and it’s probably about to die. The only thing you do is perhaps try to log the execption first.
Boneheaded exceptions alert the developer that they have done something wrong. Methods should throw some kind of
ArgumentException when the arguments provided don’t meet the method preconditions. Code should be written so that exceptions like
IndexOutOfRangeException are not thrown at all. These exceptions are an early warning of problems with the code.
When writing a method, it is usually a good idea to explicitly check preconditions and throw an
ArgumentException if they are not met, even if the arguments would cause an exception to be thrown later. Explicitly throwing an
ArgumentNullException with the name of the argument at the beginning of a method gives more information about what the problem is than the
NullReferenceException that might be generated further down in the method (or in the call stack).
Vexing exceptions are any exceptions thrown in non-exceptional circumstances. In the example of
Int32.Parse, this method is often used to parse strings that come from user input, or from other external sources. It is to be expected that these strings will often not be parseable. If possible, use a method that doesn’t throw a vexing exception (e.g.
Int32.TryParse instead of
Int32.Parse). If the method can’t be avoided, it can be wrapped in a method that catches the vexing exception and returns a
bool or an error code to indicate whether the operation succeeded.
Exogenous exceptions are those that are truly exceptional (unlike vexing exceptions), outside the control of the programmer (unlike boneheaded exceptions), and which occur because of conditions outside of the program (unlike fatal exceptions). These are the only exceptions you should usually be catching, and, other than boneheaded exceptions for validating inputs, the only exceptions you should usually be throwing. Exogenous exceptions are given their own treatment in the next post.