Simple Talk is now part of the Redgate Community hub - find out why

Taking Exceptions

Steve looks at the requirements that are usually in force for the support of a team-based 'production System' and suggests a way that exception-handling in C# can be used to provide robust error reporting

Exceptions End to End

I’m going to describe, and provide, a simple framework for a common and systematic approach to the use of exceptions. There are many ways of skinning a cat in Software Development  This article contains just one man’s view. It is a view resulting from twenty plus years in the field, untold number of projects and interaction with 100 plus developers, but it is still just a personal view. With that being said, “Take and use what you might find of value, and throw away that which has no value.”

The source for this article is available at http://www.cabercomputing.com/Downloads/ExceptionsEndToEnd.zip, or via the link at the bottom of the article.

Who:

The class library, business layer developer has a responsibility to other developers who will use their APIs. Typically, good class library designers do a fair job of documenting their APIs but do a miserable job of documenting the exceptions which could be thrown during the execution of their library.

Additionally, little thought is given for the person who will have to suffer through the task of maintaining the class library.

What:

You will need to be concerned with the following points, when developing a common system approach for exceptions.

  1. All of your exceptions should inherit from one common exception class.
  2. A central point is needed where all exceptions are to pass through before throwing. This is useful to provide:
    • A single point for setting one breakpoint before any exception is actually thrown.
    • One spot to log and/or forward the exceptions via email.
  3. A unique identification is needed for each and every exception which could be thrown. A defined, discrete list of all possible exceptions relative to the class being used by the caller is needed.
  4. A message is needed that can be defined independently from the exception. Seldom is an application developer capable of defining a message which is appropriate for the end user.
  5. A loosely coupled approach is needed to allow for parameter substitution in the message.
  6. An approach which can support internationalization (globalization) of messages.
  7. Explicit visual clues for the maintenance programmer as to the requirements for successful execution of each and every method in a class.
  8. Critical classes, like entry points to a tier, require special handling. Typically this is done so as not to expose private details to the caller, like a connection string to a database.

Let’s take a look at three classes to pull this off.

First of the three classes is a base class which all of your future exception classes should inherit from:

The ExceptionX provides the fundamental piece missing from System.Exception, and that is a unique identifier..

Within a single business layer, we might have 100-1000 different cases which need to be thrown as an exception. The caller needs to distinguish one case from another. Obviously we are not going to define 100 to 1000 different exception classes; therefore we need to properly distinguish one case from another. The property “ErrorType” does just that!

It is better for the caller if you create one exception class, which derived from ExceptionX, for each of the classes in your library. This allows the caller to catch any and all exceptions forcefully thrown by your class with one single catch statement.

Bertrand Meyer, creator of one of the first object oriented languages, “Eiffel”, had an interesting concept which was built into that language. A developer of a method in a class could:

  1. Define pre-condition(s) which must be met(true) before the body of the method was executed,
  2. Define post-condition(s) which must be true after the body of the method has executed, and tested by the language before successfully returning to the caller.
  3. Define zero or more invariants which are always to be true, or the language would throw an exception.

Since the C# language does not support this concept out of the box, the second of the three classes, “AssertX” provides a somewhat similar functionality.

One thing to note: the Throw method of the AssertX class calls Resource.Build(…).  This brings us the last required class in the framework.  The last class is called “Resource”, and it performs the magic of building the exception’s message and creating the actual exception to be thrown (More on this later). 

The actual implementation of the Resource class is beyond the scope of this article.

The Resource class will build a default message for the Error Enum. 

Example #1:

AssertX .Throw(Error .InternalUnknownErrorOccurred);         becomes

“Internal unknown error occurred.”

Example #2:

AssertX .Throw(Error .InternalArgument_Null, “aMsg”);        becomes

“Internal argument(aMsg) is null.”

Example #3:

int count = 32;

AssertX .Throw(Error . InternalInvalidValue_For_, count, “count”);         becomes

 “Internal invalid value(32) for(count).”

Additionally, the Resource class will look for a resource entry for Error Enum in a resource file named “ResouceExceptions” within the assembly in which the Error Enum is defined. 

Example #1:

ExceptionX . Error . InternalUnknownErrorOccurred     resource name is

“ExceptionX__InternalUnknownErrorOccurred”            resource value of

“An internal error has occurred!  Unknown error.”

Example #2:

ExceptionX . Error . InternalArgument_Null            resource name is

“ExceptionX__InternalArgument_IsNull”                 resource value of

“An internal error has occurred!  Argument: {0} was null and that is not allowed.”

Example #3:

ExceptionX . Error . InternalInvalidValue_For_        resource name is

“ExceptionX__InternalInvalidValue_For_”               resource value of

“An internal error has occurred!  An invalid value of {0} was supplied for {11}.”

Remember, resource entries are optional. This allows the development process to move along efficiently, since at any future point in time any message for any error can be changed, without affecting the code base.

Where:

Where does a user of your class find the possible errors/exceptions that might be thrown?

Every class should contain a master list of all possible exceptions that can be thrown during its execution. The designer of the class must clearly document the exceptions which can be thrown for each method and/or property using only those items contained in the class’s master list.

Each item in the class’s master list must:

  1. Be unique to the list.
  2. Should be descriptive and not cryptic.

    Example:

The master list is to be contained as an element of the class which this list is for. My convention is for the master list to be contained in one single enum which is always named Error. The developers who are using your class will quickly and easily be able to see the complete list of items via IntelliSense on “{YourClassName}. Error”. Here is a little sample class demonstrating the approach.

As a developer of a class, you have an obligation to the caller of your class to provide quality and resilent code.

Three approaches can be taken when an exception occurs in the middle of the execution of your code.

Approach 1: Ignore an exception – Let the caller deal with it

This is the simplest approach, and the one most often taken by new and non-seasoned developers. It requires almost no thought but, unfortunately, it generally results in heartache for the developer who must call your code. Since the class library’s designer has not identified the exceptions, it is left to the caller of your code to determine what exception(s) might occur during the execution of the code.

This approach is discouraged unless you are :

  1. Willing to document all possible exceptions and
  2. Very confident that your class will not be left in an unpredictable/inconsistent state, which might affect future calls.

Approach 2: Try-Catch / Re-tag

This is a good approach. It requires you to wrap a block of your code in a Try-Catch block. In the catch section of your code, it is high suggested that you “re-tag” the exception you have caught to one of your nested exceptions, of type “ExceptionX”. While the re-tagging is optional, it eases the work incurred by your caller. If you choose not to re-tag, then you must document the fact that the lower-level exception can be thrown. Additionally, the catch portion of the Try-Catch block affords you the opportunity to restore your object to a known and reliable state, so that it can service future calls correctly.

NOTE: There is a performance hit taken every time an exception is thrown and caught. While generally this is not significant, it can become so when the caller has a tight loop that calls your code to determine the percent of acceptable cases.

Approach 3: Prevent lower level exceptions from occurring

Where possible prevent lower level exceptions from occurring. Try to verify as many things as possible before changing any state in your object. This help minimize your effort to restore the object to a consistent and known state before returning to the caller. This approach when used with the framework layout in this article, results in few lines of code then the try-catch and re-tag approach.

No one single approach is the right for every case. It is up to the developer to choose the right approach for the task at hand. It is strongly suggested to try to use Approach 3 first and then when necessary employ Approach 2, and seldom if ever use Approach 1.

Why:

Why do some of the items in the Error enum contain underscores? Each underscore is used to represent where a parameter substitution is planned to occur at runtime when the exception is actually thrown.

Why do some of the items in the Error enum start with the word “Internal”? As a developer of this class, I realized those items which are truly not an end user error. This area can be a little gray sometimes, but give it your best shot. Let’s take the item, “InternalInvalidArgument_”, it would be used when argument passed to a method must not be null.

Why use an enum? They are:

  1. Cheap (in terms of memory),
  2. Convenient, easy to use for both the class developer and caller (intellisense)
  3. Provide strong type checking
  4. Automatically numbered and
  5. Provide the unique identity

I have seen people use the Message property of the Exception class to try distinguish one error from another. This approach is brittle and prone to errors, since changes to the original message text requires subsequent client code changes. The purpose of the Message should be for the end user and note the developer of your class. Just as your class provides a contract to the caller for its interface, the caller needs a guarantee as to what specific exception(s) could possibly be thrown from each of your methods and properties. Additionally, each exception must be uniquely identified. The handle, the client or caller uses to distinguish one exception from another is critical.

The items in the Error enum perform a few different purposes:

  1. Document the list of possible exceptions
  2. Provides a unique identifier for the caller
  3. Provides the basics for automatically generating the default message of the exception
  4. Provides the key for a possible custom message(s) that can be found in a resource file

Each item in the Error enum can optionally be backed by an entry in a resource file. This allows for custom message(s) to replace any or all default automatically generated messages.

Your class(es) will be hosted in an assembly (either a DLL or executable), and there should be at most one base resource file to contain all possible custom error messages for the classes in the assembly. Satellite resource files should be used to internationalization (globalization) your error messages.

Let’s explain:

Any method which might throw an exception should use the AssertX class. This will funnel all exceptions through one single point in the AssertX class. This allows the developer to:

  1. Document things which must be true before the body of the method is executed
  2. Document things which must be true before returning to the caller
  3. Set one breakpoint to examine all application exceptions before being thrown.

The use of PreCond() and PostCond() provides clean, crisp and concise expression, while eliminated nasty clutter-some conditional “if(…)”statements.

NOTE: All of the AssertX methods take an optional number of parameters (param object[] aParms). These parameters are used in the parameter subsutition process. It is the developer’s responsibility to supply the correct number of and order of parameters for the specific Error enum used.

These explicit visual clues as to the requirements associated with each method are extremely helpful to the maintenance programmer for years to come. Additionally, a custom tool (Microsoft, are you listening?) could easily parse the code and include a XML comment of all possible exceptions that might be thrown by each and every method and/or property.

The AssertX class is responsible for causing the creation and throwing of the actual exception. The AssertX calls the Resource  class to create the exception, the Resource class checks the appropriate resource file for a custom message for the actual  Error enum passed. Each item in the Error enum can optionally be backed by an entry in a resource file. This allows for a custom message to replace the default automatically generated message. The resource file provides support for internationalization (globalization) of your error messages. A resource file breaks the dreaded tight coupling between programmer handling of the error and its message.

If no custom message is found, it builds a default message based on the  Error enum. Typically in the development process, the developer take a heads down approach and does not immediately create a custom message in the resource. This action can be taken at a later point in time, and sometime by someone other than a developer.

Next the Resource class performs any parameter substitution on the message. Finally, the Resource  class creates the exception and returns the instance to the AssertX class.

Before the  AssertX class throws the exception, it could log and/or email the exception.

How:

Developers using your class need an easy way to catch exception(s) specific to your class. They could have a try with a catch of System.Exception  or even of ExceptionX, but this is not very elegant. Adding a nested exception class to your class, , UUser,solves this problem.

So how does the nested class User.ExceptionX  come into play? The Resource class’ method  Build()  creates both the message for the exception and the exception itself. The method looks at the type information of the enum passed.

Example:

This will eventually call Resource. Build(User.Error.InternalInvalidArgument_) which will in turn look for an optional class called User.ExceptionX that inherits from CaberComputing.Utilities.ExceptionX.  If found then the Build() method will instantiate it, else instantiates CaberComputing.Utilities.ExceptionX.

 

The Resource.Build()  requires that all classes derived from CaberComputing.Utilities.ExceptionX.

CaberComputing.Utilities.ExceptionX must have a constructor with the following prototype:

The ultimate form of misuse of the term “reuse” is to basically cut and copy the following code to any class where you want to define a nested exception class, which the Resource class wil use.

the Resource class wil use.

Adding a snippet to Visual Studio to insert the above code into you class can ease the pain of the approach.

As a designer of a class library, HOW do you ensure your code behaves correctly? Unit Test, of course. Unit test provide other developers excellent examples as to how to use your class. All unit tests relative a single assembly should be placed in one test project. I like the test project name to be zTest.{AssemblyName}. This forces all of my test projects to be displayed at the end of the list of all projects in the Solution Explorer window of Visual Studio.

Your unit test should have at least one test for each method in your class to ensure successful execution and, additionally, one test for each possible exception. Typically, I like to code the test for the exceptions first, and prove they all work, then test one or more successful scenarios for each method.

So let’s get to an example for the User. Record() method.

Unfortunately, NUnit support for testing of expected exceptions is less than desired. The first test Record_InternalInvalidArgument() will in fact throw an exception of the type User­.­ExceptionX, , but what is really desired is for NUnit to verify the exception is a User.­Error.­InternalInvalidArgument

Currently, NUnit 2.2.4 does support the following prototype: [ExpectedException(Type exceptionType, string Message)]. This is insufficient since our messages can be contained in a resource file and developers neither maintain nor control the text associated with any message. Yes, as developer, I could get the text from the resource file and use that, but a great many of our messages require parameter substitution at runtime.

The following is an interface, I have suggested to the NUnit folks:

Notes:

When is special attention/handling required?

Strategic classes need special attention, and they are:

  1. Classes which are an entry points to a physical and/or logical tier
  2. Any class which encapsulates a single business process

Special processing:

  1. The methods of these classes need to be wrapped with a try/catch with optional logging.
  2. When necessary, transform (re-tag) any exception which exposes a detail which the caller should not see.
  3. Throw a new exception versus re-throwing an exception, so as not to expose the stack trace associated with the original exception.

If you derived a class from another class which has virtual methods, when you override a virtual method you should/must adhere to the interface of the base class’s method. This includes the list of possible exceptions which might be thrown from those virtual methods. You cannot throw any exceptions other than those explicitly documented in the base class. When designing classes with virtual methods, adding a “catch-alll” error type (e.g. InternalUnknownErrorOccurred) to the list of possible exceptions (is a good idea, can be helpful).

How you log in to Simple Talk has changed

We now use Redgate ID (RGID). If you already have an RGID, we’ll try to match it to your account. If not, we’ll create one for you and connect it.

This won’t sign you up to anything or add you to any mailing lists. You can see our full privacy policy here.

Continue

Simple Talk now uses Redgate ID

If you already have a Redgate ID (RGID), sign in using your existing RGID credentials. If not, you can create one on the next screen.

This won’t sign you up to anything or add you to any mailing lists. You can see our full privacy policy here.

Continue