A comment thread on LinkedIn today ended up pointing me to an article by Eric Lippert about Vexing Exceptions.
The article is a little old (around 2008), but I found it to be one of the best-reasoned articles on exception handling I’ve seen.
In it, he basically categorizes exceptions into 4 types:
- Fatal – You can’t predict and can’t do anything about it
- Boneheaded – Results from a bug in your code
- Vexing – Results from a bad design (like Integer.Parse)
- Exogenous – Results from outside factors, but can be handled by your code.
Fatal and boneheaded exceptions you can’t do anything about (at least not initially). The best defense against them is a good logging framework that allows for multiple logging levels, like off, normal, and debug for instance.
Vexing Exceptions are those that result from a poor API design. The example Eric gives is the Integer.Parse function. It will throw an exception when the parsed text doesn’t result in an integer, but that’s the whole point of parsing the text in the first place! With vexing exceptions, I usually try to find an alternate implementation that simply doesn’t generate the exception. When that’s not possible, you wrap the block in a Try and just eat the exception. It’s the only other choice you have.
Exogenous exceptions are probably the most typical exceptions you’ll handle in normal code. These are exceptions that you can generally deal with when they occur, but you can’t write code such that they won’t occur, because they are caused by factors outside your code. The typical example is a File Not Found error. Your code can usually easily recover from this type of error, but there’s simply no way to code around it such that you can guarantee it won’t happen.
Which actually leads me to an additional conclusion Eric touched on.
Pre-coding Exceptions (or Why you shouldn’t)
Often, I’ll see (and have certainly written more than once) code such as:
if not FileExists(MyFile) then
Msgbox “The File Doesn’t exist”
else
OpenFile MyFile
Read/Write/Whatever
Close MyFile
End If
This is an example of an exogenous error that I’m trying to “Code around”, using the FileExist function to detect whether the file is there before trying to open it.
Unfortunately, because there’s no way to predict when the file in question might be deleted, you can still get a file not found error at the OpenFile line, because other code may have deleted the file between the execution of the FileExists test and the OpenFile command itself.
Because of this, it’s often just a waste of time and code to put the FileExists test in, since to be truly covered, you also have to trap for the FileNotFound exception.
And since most exogenous exceptions fall into this pattern, I generally don’t try to code around them anymore. Rather, I just put the appropriate Try Catch blocks in place and deal with those exceptions that way.
Try
OpenFile MyFile
Read/Write/Whatever
Close MyFile
Catch Ex as FileNotFoundException
Msgbox “The File wasn’t found”
Catch Ex2 as Exception
‘Handle other exceptions as applicable
End Try
To do otherwise means you’re likely to miss exceptions occasionally, or you’ll be doing more coding than necessary (and repeating yourself, an even worse situation).
Typical Application Types
What Eric’s post didn’t touch on was exception handling strategies for different application types.
Most applications will fall into a few distinct types. Granted, you’ll always have drivers, embedded systems, and others that have even more specific error handling needs, but I generally see these types most often.
Command line applications
Your typical command line app is single purpose, with a host of command line switches to control its behavior. For these apps, I generally set up a single TRY CATCH in the sub Main to log or display to the console any unhandled exceptions, and then use a few scattered try blocks throughout the processing code to handle any vexing or exogenous exceptions. Everything else, just let fall back to the main Try block, since the run has basically been fatally terminated anyway.
Event driven UI applications
For these, I usually setup a thread exception handler and a process wide exception handler, just to prevent the application from failing completely with no logging of any sort.
Then, I always setup try blocks in the sub Main, and in every non-trivial event handler, at a minimum. “Non-trivial” here means any event handler containing code or calls to code that could possibly result in an exception.
And finally, I use a small scattering of Try blocks to deal with code where I expect there “could” be an exception, even though there normally won’t be, but that I can adequately handle in any case.
For this scenario, I’ve found that an AOP (aspect oriented programming) approach, using something like PostSharp or Spring.net, is a quick, easy and very maintainable way to get these try blocks in place.
Function Libraries
Since function libraries typically don’t (and shouldn’t) display any UI components, I generally let any unexpected exceptions bubble out of the library code back to the caller naturally.
On some occasions, it’s necessary to expose new exceptions from the library. In those cases, I usually create an MyLibrary.Exceptions namespace, containing all the custom exceptions that might be thrown from my library, and then throw them when appropriate.
Logging
No discussion on exception handling would be complete without mentioning logging. Any non-trivial app really should have a logging framework in place. Two of my (free) favorites are Log4Net, and NLog.
They’re both very similar, very capable and very easy to use. Log4net is the “old guy on the block” having been around for a number of years now. NLog is the newcomer, but very capable none-the-less.
Both make configurable logging so trivially easy, it’s almost criminal not to use them.
And, realistically speaking, exception handling is a much more powerful concept when coupled with a comprehensive logging strategy.
Finally, logging is a perfect use case for AOP, so definitely investigate some of the AOP frameworks when you start down this path.