Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

To Err is Human… to Handle, Divine

3.54/5 (5 votes)
9 Apr 2007CPOL10 min read 1   73  
How do you handle exceptions? Do you reuse your solution?

Exception handling theory – My version

How many times have you heard exceptions should only be thrown during exceptional events? I am aware there is a programming cult who continuously try to force this opinion into my head. In fact, many developers and architects have subscribed to this heuristic. Don't! This article is to discuss what other alternatives exist, and how to use them. As a side note, and a plug for CodeProject, if you have not looked at Daniel Turin's article, Exception Handling Best Practices in .NET[^], you should.

What is an error

Is there a difference between an exception and an error? The Exception class is the root of an entire class hierarchy of exceptions in .NET; therefore, it would follow that we should use this definition. In contrast, an error is a program state which causes the need for an exception object. In this terminology, an error can exist but never be discovered. If I were to pass two variables "A" and "B" to a function that performed the division of them and I were to place a value of zero in "B", the value of "B" would be an error, while the fault generated by the ALU would be the start of the exception. So, an exception and an error are not the same thing, an error is an undetected situation that could cause an exception.

Image - Stack.gif

Abstraction Level

If we were to assume an application had many levels of abstraction, then different errors could exist within those levels! Return for a moment with me to our previous, but simplistic, division example. Three levels exist: the client of the said function, the function code, and the implementation of the division operator. The error occurred in the client code, where the client provided zero as an argument to the calling function; however, the exception was discovered in the implementation of the division operator.

Similarly, we could have a situation where the client used the open file dialog box. If the user were to choose a file that does not exist, and the client were to pass that value to a file open operation, then we have an error at the user level, but the exception at the implementation level.

Error handling strategies

Rarely do we, business programmers, build implementation code. As business developers, we are generally more concerned with the middle layer of abstraction; above this layer, we are talking to GUI code, and below, implementation code. Therefore, we generally don't care about errors, as we don't know about them, nor are we overly concerned about specific exceptions, because we cannot solve them. However, exceptions generate alternate code paths though our source, and this we care about very much!! So, the existence of exceptions is an important part of developing business software.

Try, Catch, Finally (TCF) is a fairly elegant solution to exception handling; the catch clause will allow us to trap an exception, if we care about it, and resolve the error. But, we also have the finally clause which allows us to write code that can clean up regardless of an exception event.

Note: It is good programming practice to commonly use finally blocks.

Rarely Swallow Exceptions

Although Turin's article has good coverage of handling and discusses this point, it is so important I wanted to pay special attention to it. Since we have discussed our relationship to errors and exceptions, it should be noted that having empty catch blocks attached to the Exception class in your code is a horrible habit, because it prevents the above layer code from discovering the exception and solving it. All abstraction layers are required to follow their interface contract. At the most generic level, it should read as follows: I, the caller, will provide you, the function, with valid arguments if I want a return value. However, if I give you bad information or invalid arguments, an exception is an acceptable return value.

Note: Almost all catch blocks of type Exception should re-throw the exception using the throw keyword.

.NET 2.0 Added a Data Collection – Use It

In the perfect production system, all exceptions should return and be handled by the abstraction level where the error occurred. However, this is generally not the real-world case, and as a result, .NET has implemented a concept called Unhandled Exceptions. These are exceptions which are not handled within the abstraction stack, and if left unsolved, will crash our application.

Sometimes, tracking these items down can be quite challenging. .NET 2.0 added a <string, object> collection called data to the exception class. All catch clauses which use the throw keyword should append runtime information to the data collection; moreover, this collection should be dumped if the exception ever enters the unhandled state.

Why did I Write ErrorHandlerSubSystem

Exception.ToString() was not advanced enough for my last handful of gigs, as I was using the data collection to store useful runtime values. I wanted that collection in my log file. After using the Enterprise Exception Code Block (EECB), I decided that the tight integration between the other Enterprise blocks was cumbersome, and developed my own error handling solution. This solution is not a replacement for the enterprise exception handler, my solution is designed for the medium to large corporate application.

Simplify Enterprise Application Block

So you may be asking yourself what is the difference between the EECB and my solution. The primary difference is my system only handles Unhandled Exceptions. Therefore, I do not hide or alter exceptions, nor do I have a cool configuration writer.

Fundamentally, the EECB's concept of a policy maps to an action. In contrast, the Error Subsystem uses policies as a link between an exception type, a formatting style, and an error handling action.

How Did I Write It - Architecture

Image - Formatter.gif

What Would Classify as a Good Formatter

A Formatter is a way to transform an exception into another format. Originally, this concept was designed to present exceptions to users. I could have a formatter that provides a minimal amount of information to the user, or a formatter that verbosely details the data collection of the exception. If I were going to use a custom CSV exception log file, I could use the formatter to build that CSV format.

At format time, the FormatException function is handed an ErrorFormatterArgs argument which contains a text stream for output, and the exception to be processed. This function should format the exception as desired and place it into the given stream.

Image - Handler.gif

What Would Classify as a Good Handler

The process of handling an error is a two step process, and getting the exception in the correct format is the first step. The next step would be to perform a useful action with the exception. Many Microsoft applications send exception information back to headquarters, so they can be evaluated and repaired. If there was a formalized bug tracking software at your company, you could develop a custom handler to append all unhandled exceptions to the bug tracking software as new items to be evaluated.

The custom ErrorHandler is slightly more complicated than its formatting peer, as it must be derived from the base class ErrorHandlerBase. During instantiation, the constructor will receive an ErrorWriterConstructorArgs argument, which is currently reserved for future use. However, this constructor will only be called once per application lifetime just like the formatter object, as the entire Error subsystem is a singleton pattern.

At processing time, doErrorProcessing is passed ErrorHandlerArgs as an argument. This object contains an input stream containing formatted data and the original exception object

Note: Passing the original error to the handler is an intentional abstraction violation. Please don't abuse this feature.

Policies and Configuration File

One of the simplest formatter, handler combinations I use is the UserFormatter and the ErrorMsgBoxWriter. The formatter only sends Error.Message to the stream, and the handler simply passes the contents of the stream to the message box.

Note: for most unhandled exceptions, this is not overly useful.

Since these policies needed to be flexible, they are implemented in the configuration file, similar to the ConnectionString section or the AppConfig section. "Add" is the only interesting configuration tag and it looks like Snippet 1.

XML
<add Exception="Exception Type here">
 <Formatter type="Reflected type to load"/>
  <Handler type="Reflected type to load" [PropertyName = "PropertyValue"] />
</add>
Snippet 1

When the error subsystem initializes, it builds a list of polices, and each policy contain references to a formatter and handler. Part of the loading process is to instantiate the object listed in the configuration file then set all of the listed properties of that object to the value in the configuration file.

Implementation pitfall: all properties need to have setters and they must be able to deal with a string argument.

App.Domain Hooks

Although not long, one of the more interesting functions is the ErrorHandler.InstallApplicationErrorHandler(). Its goal is to link to the current AppDomain's UnhandledException event and fire the global error handling function when the application domain finds an unhandled exception.

Walk Through

Let us assume an error was discovered within the code, and an exception has been created and begins traveling up the call stack. At some point, if not handled with, it will reach the application domain, and the runtime will attempt a last ditch effort to solve the problem. It will fire the unhandled exception event. If you are familiar with the Visual Studio debugging environment, the "Unhandled Exception" balloon appears when this event is fired and the offending line of code is highlighted in yellow. Alternatively, if the error subsystem is installed, then the global error handler will be invoked. This will evaluate all of the policies bound to the raised exception type, and all of the corresponding formatters and handlers will be invoked.

However, as exceptions are built in a shallow exception hierarchy, the base class of the thrown exception is discovered, and we run though the process, a second time, as if that error was raised. Eventually we will raise the offending exception, as if it were of type Exception. This enables us to build a concise list of policies; if we attach a policy to the Exception type, it will run for all unhandled exceptions.

How can ErrorSubSystem be Improved

Implementing a few more basic handlers would be nice, an SMTP and an SMS handler; however, having even more advanced handlers that could append messages to error channels of Message Oriented Middleware's such as MSMQ or Tibco would also add significant business value.

It may be nice to add the flexibility to have non-string setters in the handler interface.

Lastly, it would be fun to develop a user interface which would alter the policy configuration file, similar to the MS version.

Bibliography

Revisions

  • Original post (4-9-07).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)