Design Guidelines Update: Exception Throwing

Exception Throwing

Exception throwing guidelines described in this section require a good definition of the meaning of execution failure. Execution failure occurs whenever a member cannot do what it was designed to do (what the member name implies). For example, if OpenFile method cannot return an opened file handle to the caller, it would be considered an execution failure.

Most developers have become comfortable with using exceptions for hard error cases such as division by zero or null references. In the Framework, exceptions are used for both hard errors and logical errors. At first, it can be difficult to embrace exception handling as the means of reporting all functional failures. However, it is important to design all public methods of a framework to report method-failures by throwing an exception.

There are a variety of excuses for not using exceptions, but most boil down to the two perceptions that exception handling syntax is undesirable, so returning an error code is somehow preferable, or that a thrown exception does not perform as well as returning an error code. The performance concerns are addressed in the performance section below. The concern over syntax is largely a matter of familiarity and should not be a consideration. As an API designer we should not make assumptions about the familiarity of the application developers consuming our code.

ý Do not return error codes. Exceptions are the primary means of reporting errors in frameworks.

þ Do report execution failures by throwing exceptions. If a member cannot successfully do what is designed to do, it should be considered an execution failure and an exception should be thrown.

þ Consider terminating the process by calling System.Environment.FailFast (.NET Framework 2.0 feature) instead of throwing an exception, if your code encounters a situation where it is unsafe for further execution.

ý Do not use exceptions for normal flow of control. Except for system failures, there should generally be a way to write code that avoids exceptions being thrown. For example, you can provide a way to check preconditions before calling a member to allow users to write code that does not throw exceptions.

ICollection<int> collection = …

if(!collection.IsReadOnly){

    collection.Add(additionalNumber);

}

The member used to check preconditions of another member is often referred to as a tester and the member that actually does the work is called a doer. See performance section below for more information on the Tester-Doer Pattern.

There are cases when the Tester-Doer pattern may have an unacceptable performance overhead. In such cases the so called TryParse Pattern (see section below) should be used.

þ Consider performance implications of throwing exceptions. See section below for details.

þ Do document all exceptions thrown by publicly callable members because of a violation of the member contract (rather than a system failure) and treat them as part of your contract. Exceptions that are a part of the contract should not change from one version to the next.

ý Do not have public members that can either throw or not based on some option.

Type GetType(string name, bool throwOnError)

ý Do not have public members that return exceptions as the return value or an out parameter.

þ Do set all the relevant properties of the exception you throw.

þ Consider using exception builder methods. It is common to throw the same exception from different places. To avoid code bloat, use helper methods that create exceptions and initialize their properties. For example:

class File{

   string fileName;

 

  public byte[] Read(int bytes){

      if (!ReadFile(handle, bytes))

            throw NewFileIOException(...);

   }

 

   FileException NewFileException(...){

      string description = // build localized string

      return new FileException(description);

   }

}

ý Do not throw exceptions from exception filter blocks. When an exception filter raises an exception, the exception is caught by the CLR, and the filter returns false. This behavior is indistinguishable from the filter executing and returning false explicitly and is therefore very difficult to debug.

ý Avoid explicitly throwing exceptions from finally blocks. Implicitly thrown exceptions resulting from calling methods that throw are acceptable.

1.1.1 Choosing the Right Type of Exception to Throw

þ Consider throwing existing exceptions residing in the System namespaces instead of creating custom exception types.

þ Do create and throw custom exceptions if you have an error condition that can be programmatically handled in a different way than any other existing exception. Otherwise, throw one of the existing exceptions.

ý Do not create and throw new exceptions just to have ‘your team's’ exception.

þ Do throw the most specific (the most derived) exception that makes sense. For example, throw ArgumentNullException and not its base type ArgumentException if a null argument is passed.

1.2 Exceptions and Performance

One common concern related to exceptions is that if exceptions are used for code that routinely fails, the performance of the implementation will be unacceptable. This is a very valid concern. When a member throws an exception, its performance can be orders of magnitude slower. However, it is possible to achieve good performance while strictly adhering to the exception guidelines that disallow using error codes. Two patterns described in this section suggest ways to do this.

ý Do not use error codes because of concerns that exceptions might affect performance negatively.

Tester-Doer Pattern

Sometimes performance of an exception throwing member can be improved by breaking the member into two. Let’s look at the indexed property of the Dictionary class.

Dictionary<string,int> table = new Dictionary<string,int>();

int value = table[“key”];

The indexer throws if the key does not exist in the Dictionary. This can be a performance problem in scenarios where the lookup is expected to fail often. One of the ways to mitigate the problem is to test whether the key is in the dictionary before accessing the value.

Dictionary<string,int> table = new Dictionary<string,int>();

if(table.Contains(“key”)){

    int value = table[“key”];

}

The member used to test a condition, in our example the method Contains, is referred to as the ‘tester’. The member used to perform a potentially throwing operation, in our example the indexer, is referred to as the ‘Doer’.

þ Consider the Tester-Doer pattern for members which may throw exceptions in common scenarios to avoid performance problems related to exceptions.

TryParse Pattern

For extremely performance demanding APIs, an even faster pattern than the Tester-Doer described in the previous section should be used. The pattern is to adjust the member name to make a well-defined test case a part of the member semantics. For example, DateTime defines a Parse method that throws if parsing of a string fails. It also defines a corresponding TryParse method which attempts to parse, but returns false if parsing is unsuccessful and returns the result of a successful parsing using an out parameter.

public struct DateTime {

    public static DateTime Parse(string dateTime){

        …

    }

    public static bool TryParse(string dateTime, out DateTime result){

        …

    }

}

When using this pattern, it is important to define the ‘try’ functionality in strict terms. If the member fails for any reason other than the well defined try, then the member must still throw.

þ Consider the TryParse pattern for members which may throw exceptions in common scenarios to avoid performance problems related to exceptions.

þ Do provide an exception-throwing member for each member using the TryParse pattern.