Exceptions – 2

At this point, it seems appropriate to put some terminology in place for my ongoing discussion of throwing and handling exceptions. As a matter of fact, I will be providing two sets of terminology for the price of one!

These sets of terminology are by Krzysztof Cwalina (leader of the effort to develop API guidelines) and Eric Lippert (senior member of the team designing the C# language). I think it’s fair to say that between the two of them there is a lot of experience with how to do things and how not to do them using C# and .NET.

Krzysztof Cwalina classifies exceptions into “usage errors” and “system errors“, the latter of which can be split into “logical errors” and “system failures“. Eric Lippert classifies them into “fatal“, “boneheaded“, “vexing” and “exogenous“.

All that may just sound like a jumble of (colourful) word-soup, so the following sections will make the terms and what they mean a bit more concrete.

Usage Errors” / “Boneheaded Exceptions
These exceptions signal that there is a problem the calling code could have avoided itself. As a result this is a bug in the caller, not a fault in the called code. Typically these are the result of broken preconditions and invariants.

public void DoSomething(string nonNullValue)
{
    if (value == null)
        throw new ArgumentNullException("value");
    ...
}

You must make sure your code passes this method a non-null value; if you do not, then the resulting exception is your own fault. You broke the contract.

System Failures” / “Fatal Exceptions
These exceptions cannot be handled under any circumstance. They signal a fundamental problem with the state of the virtual machine, such as “Out of Memory” or “Thread Aborted” or “Type Load” exceptions that can occur at almost any instruction in your program.

The only correct thing to do with these exceptions is to let them climb up the stack until they eventually terminate the program. There is nothing an application should try to do to recover from these, because there is no sensible way to recover. This is also why catching “Exception” is so heavily frowned upon.

There are ways to write general recovery handlers, but they have to follow a very specific pattern to make sense. More on that in a later post.

Logical Errors” / “Exogenous and Vexing Exceptions
These are the “real” exceptions. They indicate that the method could not make good on its promises in some fashion. You asked the method to open a file for reading, but the file doesn’t exist; too bad! You asked the method to parse a string into an integer, but there were letters in the string; oops!

The reason that Eric Lippert presents two options here is specifically for my second example. Some exceptions thrown by methods that cannot satisfy their contract indicate that the API was just badly designed; sometimes you have to expect certain failures, and code accordingly.

using (var reader = new StreamReader("DataFile.txt"))
{
    var line = reader.ReadLine();
    try
    {
        var value = int.Parse(line);
        ...
    }
    catch (FormatException ex)
    {
        // Is this Exogenous or Vexing?
        ...
    }
}

As the exception handler asks… is this exception exogenous, or vexing? I really can’t say, because it depends on the context. If the “DataFile.txt” was created by an end user, and is supposed to contain a single line with an integer value on it, then this is almost certainly a vexing exception, and use of the “int.TryParse(...)” method would have been more appropriate. Betting on a human-generated file to contain correctly formatted input is wishful thinking; you have to assume there may be problems.

If however that file were produced by another application, then this may very well be an appropriate way to deal with the situation. We can safely assume that if the other program produces an integer one time, it will likely do so every time, and if it doesn’t that is genuinely worthy of an exception and associated logic (albeit in reality probably a few levels further up the stack than my simplistic example).

Up Next…
In the next post I hope to distill down some initial advice on these three categories of exceptions. I will not treat vexing exceptions as a category from here on in, since a vexing exception really indicates an incomplete API that needs to be redesigned. Usually it is just a matter of adding alternatives that allow the caller to avoid the exception in favour of a more complex method contract, similar to the way the “TryParse” calls were added in the .NET Framework to many classes that didn’t have them before).

2 thoughts on “Exceptions – 2”

  1. Thanks Jerry – interesting read.
    It would be nice if we could catch “types” of exceptions. 🙂

    1. That’s actually really trivial; neither Boneheaded Exceptions, nor Fatal Exceptions should ever be caught. Vexing Exceptions are design flaws. So all that remains are Exogenous Exceptions, which are exactly the only ones that should ever be caught.

      I haven’t addressed which exceptions to throw where and how though, or when to create new exception types (and from which base-class) and how to structure code that handles them so that the problem signaled by an exception does not get worse. Far too much for a single post, but it will appear, one step at a time.

Comments are closed.