A Pattern for Handling Exogenous Exceptions Code

I have found myself writing quite a few classes with the following properties:

  • The class has multiple methods that implement operations using other, private, objects.
  • Those private objects can throw exogenous exceptions (e.g. TcpClient), and
  • Exceptions thrown from those objects need to be handled in mostly the same way

To avoid duplicating exception handling code in classes like these, I find it’s convenient to write a private method to do the error handling. This method takes as an argument a delegate that it executes within a try/catch block. It may also take in some metadata about the delegate, such as an operation name, for logging and exception reporting purposes.

Each public method of the classes just calls this method, passing in the delegate to execute and any necessary metadata.

Below is an example with arbitrary class names (and also using a Log method to stand in for the actual logging methods):

public class Foo
{
  private Bar m_bar;
  private bool m_ioFlag;

  private ResultType DoOperation<ResultType>(string operationName, Func<ResultType> operation)
  {
    try
    {
      Log($"Operation {operationName} started.");
      var result = operation();
      Log($"Operation {operationName} completed successfully.")
      return result;
    }
    catch(IOException ioe)
    {
      Log($"IOException during {operationName} operation", ioe);
      m_ioFlag = false; // example code to clean up internal state in response to exception
      throw new FooException($"IOException during {operationName} operation.", ioe);
    }
    catch(BarException be)
    {
      Log($"BarException during {operationName} operation", be);
      m_bar.Reset(); // example code to clean up internal state in response to exception
      throw new FooException($"BarException during {operationName} operation.", be);
    }
  }

  public ResultType DoOperationOne()
  {
    return DoOperation(nameof(DoOperationOne),
    () => 
    {
      // implementation of DoOperationOne here, which uses m_bar and returns a Result
    });
  }

  public ResultType DoOperationTwo()
  {
    return DoOperation(nameof(DoOperationTwo),
    () => 
    {
      // implementation of DoOperationTwo here, which uses m_bar and returns a Result
    });
  }
}

The Foo class has two public methods, DoOperationOne and DoOperationTwo. Both operations make use of a private instance of Bar, whose methods may throw a BarException or an IOException. Both just pass the operation name (using the nameof operator) to the DoOperation method. The DoOperation logs that the operation start and finish, and contains the catch blocks for handling exceptions thrown by m_bar by cleaning up the Foo object’s state and then wrapping those exceptions in a FooException and rethrowing.

Following this pattern has several advantages. First, it avoids duplication of logging and error handling code, and ensures that all operations do their logging and error handling in a consistent way. Second, it enforces the advice in my previous post to have only a single exogenous exception type be thrown from each method. Third, it implements the single responsibility principle: the DoOperation method is responsible for error handling, and each of the other methods are only responsible for what is specific to that method.

Throwing Exogenous Exceptions

The previous post discussed Lippert’s exception categorization. This post focuses in particular on how methods should throw exogenous exceptions, which, in Lippert’s words, “are the result of untidy external realities impinging upon your beautiful, crisp program logic.”

The exogenous exceptions a method can throw should be treated like part of the method’s signature. C# does not enforce this: any exception can be thrown from any method. To workaround this, I prefer to have all the exceptions thrown from a method, or methods in a single class, inherit from a common type specific to that method or class.

Consider a Connection class that wraps a transport layer connection, such as a TCP or serial socket.

A partial implementation of Connection might look like this:

public class Connection
{
  public void SendData(byte[] data) {...}
  public int ReceiveData(byte[] data) {...}
}

The SendData, ReceiveData, and any other methods of Connection should only throw exceptions of a specific type, such as ConnectionException. It may throw subtypes if suitable, such as ConnectionLostConnectionException or ConnectionTimedOutException, but all exceptions it throws should inherit from ConnectionException.

Any lower level exceptions generated in Connection methods, such as IOException or SocketException, should be wrapped in a ConnectionException, like so:

public void SendData(byte[] data)
{
  try
  {
    // code that sends data here
  }
  catch (IOException ioe)
  {
    throw new ConnectionException("IOException sending data", ioe);
  }
}

Doing this ensures that a user of the Connection class is insulated from exceptions thrown from methods and classes used by the Connection object that the calling code may not know about. The caller only needs to catch ConnectionException. It may also catch specific subtypes if it will handle those exceptions differently.

Subtypes of the base exception type should be created judiciously. A subtype should be created only if calling code might handle that specific exception case differently from other exceptions.

Having a single base exception type that is always thrown by a particular method means that calling code can ensure it catches and handles all exogenous exceptions that could be thrown from a method in a single catch block. This avoids duplication of exception handling code in the caller. It also reduces the temptation to just catch Exception in the caller. This means that any fatal or boneheaded exceptions will not be inadvertently swallowed.

The exceptions thrown by a public method should be listed in its documentation comment. Adding a new exception subtype that is thrown is not a breaking change: callers should be catching the base type and so should handle the new subtype. Changing the base exception type thrown should be treated as a breaking change, the same way that changing the method’s signature should be.

Java, unlike, C#, has what are called “checked exceptions”: exception types thrown from a method must be declared, and calling code must either catch that exception type or also declare that it may throw it. It’s been a while since I’ve written any Java. I remember finding checked exceptions annoying, but I think that my feelings about checked exceptions are worth reconsidering in light of my experience with a language without checked exceptions.

Lippert’s Exception Categories

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 ThreadAbortException, and OutOfMemoryException.
  • boneheaded exceptions “are your own darn fault, you could have prevented them and therefore they are bugs in your code.” Examples are ArgumentException and its subtypes, NullReferenceException and IndexOutOfRangeException.
  • 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 int32.
  • 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 NullReferenceException and 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.