Saturday, November 11, 2006

Getting a Grip on Structured Exception Handling: Part 1

Many VB 6 developers who have made the switch to .NET continue to use VB 6's On Error error handling model. Some continue to do so because structured exception handling using Try...Catch and Throw represents a fairly daunting learning curve. Others do so because the On Error model is still supported for backwards compatibility and the old adage still applies: "If it ain't broke, don't fix it."

But there are very good and compelling reasons to learn structured exception handling. Modern programming languages have been using it for years, and Visual Basic has only recently caught on. It's a good thing, believe me. In this post, and the one that will follow, I'm going to try to explain why you want to embrace it, and how it can improve your code. Hopefully, I'll do so in a way that's clear, concise and doesn't confuse you.

If It Ain't Broke, Don't Fix It. No, Really

First and foremost, you shouldn't do a massive overhaul of your existing code just to replace the error handlers. And if you think someone's not considering that, think again. If you are, knock it off.
Refactoring your entire code base just to replace the exception handlers isn't a good idea. It's tantamount to tearing apart your car just to replace all the screws. If your software is working with the existing error handling engine, leave it alone, especially if you're not familiar with structured exception handling. You do not want to break perfectly good code because you wanted to implement a language feature that you don't fully understand. Get a grip on structured exception handling first, then use it in your next project.

As an aside, structured exception handling and On Error can co-exist in the same project. They are not mutually exclusive. Having said that, I don't normally recommend it. Mixing exception handling models tends to create code that confuses maintenance programmers; it creates a mental context switch when they're reviewing your code. Some routines use one model, some use another. This slows down maintenance of the system, and that's rarely a good thing. As you can imagine, I will not be providing advice on how to mix these models in a project.

A Review of On Error

Visual Basic's On Error error handling mechanism, which is still supported in .NET for backwards-compatibility, works in a relatively straightforward manner:
Public Sub Main()

On Error GoTo ErrorHandler

Dim x As Integer

x = 5 / 0 ' Force a divide by zero

MsgBox "Hello, World!"
Exit Sub

If Err.Number
= 11 Then
' Yep, we did this on purpose. Ignore it and continue.
Resume HelloWorld
MsgBox Err.Description
End If

End Sub

In some cases, you didn't really want to jump to an error handler. Instead, you wanted to ignore the error, because you could safely ignore it. In those cases, you used On Error Resume Next, and handled the error on the line immediately below the offending line.

Visual Basic provides the Err object, which exposes the Erl, Number, Source, and Description properties. These properties are intended to provide enough information for you to find out what happened, where it happened and what component was responsible for it.

While the On Error model was straightforward, it was looked upon with fairly universal disdain for one major reason: it involved the use of the dreaded GoTo keyword. While I won't dredge up a pointless debate about its merits, I will point out that it had one particular failing: it tended to create very complex procedures when a function needed to handle many different kinds of errors. Simply put, there was a lot of jumping around.

In addition, determining the nature of the error could sometimes be a risky business. In an ideal scenario, you could trust the error number. According to the documentation, the first 1,050 error numbers were reserved for use by Microsoft. An application or library vendor was expected to start their unique error numbers with 1051 and supply a descriptive error message along with the number. This rule wasn't always respected, however.

Vendors were also expected to provide the source of the error. But this meant that you had to query at least two properties in order to determine the true nature of an error, because two different vendors might expose the same error number. But it gets worse. Some vendors chose to use the same number for multiple errors, and distinguish between specific errors in the message. Consequently, in order to be truly certain, you had to check all three properties.

If Err.Number = x And Err.Source = y And Err.Description = z Then
' Handle the error
End If


For many developers, this seemed like overkill, so they chose to parse the contents of the description, since the likelihood of two vendors providing exactly the same message was pretty remote. This trade-off is never a good idea, however. It compromises certainty, and it brings inefficient string operations to the table. I've worked with an immeasurable amount of VB and ASP code in which this decision was made. While the code worked, it was hard to read and therefore difficult to maintain; and the lack of information about the source made it difficult to determine who was raising the error that you were trying to handle.

Someone once said that the difference between a professional developer and a hack was the way in which they dealt with errors: A professional developer takes them very seriously, and a hack plays foot-loose and fancy-free with them. I'm not sure who said that, but it struck a serious chord with me, and it changed the way I write software. When it comes to handling errors in your code, you want to be absolutely certain that you're handling the right error, at the right time, in the right way, and if you don't know what to do with an error, you leave it alone so that the caller can handle it.

Because of the way that the On Error architecture works, it is sometimes difficult to do this. You have to create lots of "jump-points" so that your error handlers return control to the correct location. If you're querying the Err object like most developers, you're doing a lot of string parsing, which can be quite tedious and creates code that is hard-to-read and performs poorly. After a while, you may start letting things go, and eventually only pay attention to the big, obvious errors. You may even get to the point where you dread diving into the error handling code because it's harder to read than the application logic.

Think you've got it bad? What about the next poor developer who's got to maintain that code after you've moved on to bigger and better things?

Moving On

So here's what we've got:

  • On Error is a simple, straightforward error handling system that works (usually).
  • There's no reason to modify existing code bases that work without an utterly compelling reason to do so.
  • On Error does pose maintenance issues, in that it is difficult to read, and tends to discourage developer discipline.
  • Error number collisions are quite frequent, meaning that developers have to rely on the Err.Source and Err.Description properties to determine the nature of the error, negatively impacting application performance and maintainability.
  • On Error is based on a trust system: it trusts the vendor or developer to correctly populate the Err object's properties, which often doesn't happen as it should.

In the next article in this series, we'll look at structured exception handling (SEH) as the alternative to On Error. We'll look at how it addresses these issues, and provides a superior means of dealing with errors in your applications.

No comments: