I once wrote an article about error handling in VB6 for Visual Basic Programmer’s Journal (Sept 2001, jeez that seems like a long time ago! {edit} and it would appear that that link is long since dead 🙁 ). It was born out of the need for a comprehensive framework for handling errors in an app I was working on at the time.
Since then, I’ve expanded the framework several times and it’s grown into something quite handy. Once I get it cleaned up a bit for public consumption, I’ll make the new and improved version available.
I’ve since been looking at the .NET error handler framework, and while it’s certainly much improved over the VB6 days, it still leaves a lot to be desired.
At the very core of handling errors is the division of error types into two distinct classes:
- Expected Errors
- Unexpected Errors
In a perfect world, every possible error is an Expected Error and there are no errors that can occur within your program that the code isn’t written to intelligently deal with.
At the other end of the spectrum, is, of course, where most textbook and magazine article sample code lies; where every error is an Unexpected Error that will crash the program.
Unfortunately, real world limitations on time, money, manpower, etc tend to force all code somewhere south of Nirvana.
The .NET fanclub used to decry VB’s on error goto as antiquated technology, and trumpeted try catch as the silver bullet of the day. And I suppose:
On Error Goto Catch
Try:
...code to try...
Goto EndTry
Catch:
...code to handle any error in the try block...
EndTry:
is quite different from
Try
...code to try...
Catch ex as exception
...code to handle any error in the try block...
End Try
as long as you’re only looking at their checksums<g>.
Sure .NET incorporates a wealth of metadata about the exception. The module, function, line number, etc are all available directly while handling the error. But something’s missing.
Oh, yeah, What about those Unexpected Errors?
.NET does have the Exception Handling Application Block and it looks very promising, although it also looks very involved. And do you really want to be able to configure exception handling policies via config files? I mean, beyond indicating the level of logging you want (verbose, medium, low, off), and the email address to send exception report to (unluckysupporttech@mycompany.com), I’m having a hard time coming up with anything else I’ve ever needed to dynamically configure in my apps for exception handling. But, I won’t say it couldn’t be useful.
In the end, I suppose the thing that bothers me most about the EHAB, and the whole structured exception movement in general, is that it really does nothing to address the Unexpected Errors in an application. You still have to wrap your entire procedure in a TRY block in order to be sure that you catch any exception raised in that procedure. Otherwise, the exception is simply propagated up the call stack to the first point that is covered by an exception handler. The good news about .NET, though, is that at least, in those cases, you can still retrieve a call stack back to the real source of the exception and log it. With VB6, no such luck without some sort of framework.
My other issue is that Try Catch style exception handling forces the declaration of how you intend on handling errors to the END of the function you’re looking at. In this age of declarative programming and metadata-attributed interfaces, that just seems so…yesterday.
Take my above comparison of try catch and on error. What actually happens to the error is stated down at the end of the procedure in both styles. Now. I can understand that approach if you have code dedicated to handling specific errors, but then, those are Expected Errors. For the Unexpected Errors, I’d rather know, declared right up front, how the prodecure intends to handle them.
That was the impetus of my article way back when:
- Provide a framework that could provide a call stack that VB6 was lacking
- Provide a means of clearly declaring how the procedure intends on handling Unexpected Errors.
Compare a typical Try Catch structure:
Public Sub Test()
Try
.....Lots and lots and lots of code....
Catch Ex as Exception
....how to handle specific exceptions, plus how to handle general exceptions...
End Try
End Sub
with a prologue style declaration like I use in my error handling framework for VB6:
Public Sub Test()
'############### ERR HANDLING #############
Dim Err As CErr: Set Err = New CErr: On Error GoTo Problem
Problem:
Select Case Err.Handle(EHF_PRESENT or EHF_ALLOWRETRY, MODULE, "Test")
Case EHE_NONE: Case EHE_RETRY: Resume: Case EHE_PASS: Resume Next: Case EHE_RESET: Resume Problem
End Select
'##########################################
.....Lots and lots and lots of code....
End Sub
The code states right up front how unexpected errors are handled (in this case, EHF_PRESENT or EHF_ALLOWRETRY indicates that the user will be shown a dialog and allowed to retry the operation). There’s a bit more verbiage here because of the requirements of VB6, but that’s not the point.
Personally, I’d prefer it if every routine in .NET could be (or maybe even had to be) attributed as to how unexpected errors are to be handled. Those that need to show a dialog would do so based on one or more “predefined” dialogs that depended on where in the app the error was thrown.
And for those apps that you never want to show an error dialog, you could instead indicated that any unexpected errors should be logged to the Event Log (or somewhere else) and then press on as best as possible.
Unlike the implications in so many magazine articles, error handling is hard. Proper error handling is like a prostate exam; you know you have to do it, but you’d really rather not bother.