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.
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
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.
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.
<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
- Abrahams, D. (2003, August 21). Error and Exception Handling. Retrieved April 05, 2007, from Boost: http://www.boost.org/more/error_handling.html[^]
- Microsoft. (2005, June). Exception Handling Application Block. Retrieved April 05, 2007, from Microsoft: http://msdn2.microsoft.com/en-us/library/ms954830.aspx[^]
- Microsoft. (2007, February 28). Patterns & Practices – Enterprise Library 3.0. Retrieved April 05, 2007, from CodePlex: http://www.codeplex.com/entlib/Release/ProjectReleases.aspx?ReleaseId=2081[^]
- Schupp, S. (2007, February). Defensive Programming and Exception Handling. Retrieved April 05, 2007, from Chalmers University of Technology: http://www.cs.chalmers.se/~schupp/courses/pfk07/lectures/exception-handling.pdf[^]
- Turini, D. (2005, February 09). Exception Handling Best Practices in .NET. Retrieved April 05, 2007, from CodeProject: http://www.codeproject.com/dotnet/exceptionbestpractices.asp[^]
- wumpus1. (2004, June 22). User Friendly Exception Handling. Retrieved April 05, 2007, from Code Project: http://www.codeproject.com/dotnet/ExceptionHandling.asp[^]
Revisions