All VB programmers feel the kiss of death when
they see a familiar run-time error message box that looks a little like this:
Microsoft Visual Basic
|
Run-time error '381':
Invalid property array index
|
If you've compiled a program to an executable (.EXE) and this sort of error
pops up, you know by now that you don't get to debug the program. It just
crashes. Is that what you want to happen? Probably not. But then, you probably
wouldn't want a program to start acting unpredictably or worse because of an
unexpected state of corruption. That's what critical run-time errors are
supposed to prevent.
But what if you actually do expect certain kinds of errors and want your
program to continue running despite them? You can "trap" and handle these
errors. To "trap" an error simply means to allow an error to occur on the
assumption that your code will deal with it. There are two basic ways to trap
and handle an error: "resume" and "go-to". They can be illustrated by the
following examples:
'"Resume" approach
Sub Demo1
On Error Resume Next
X = 1 / 0 'Division by zero
MsgBox Err.Description
On Error GoTo 0
End Sub
'"Go-To" approach
'This is not currently applicable to VBScript
Sub Demo2
On Error GoTo Oopsie
X = 1 / 0 'Division by zero
Exit Sub
Oopsie:
MsgBox Err.Description
End Sub
The key difference between these two approaches to error handling is that
On Error Resume Next tells VB you want your code to keep executing as if
nothing had happened, whereas On Error GoTo Some_Label tells VB
you want execution to jump to some specific location in your routine at any time
a run-time error occurs.
Notice the use of On Error GoTo 0 in Demo1 above? Although
it looks like a contorted version of On Error GoTo Label, it's
actually a special way to tell VB that you want to stop trapping errors and let
VB perform its own built-in handling.
Recovering gracefully from a run-time error, once you've trapped it, really
requires you to make use of the Err object. Err is an object VB uses to give
your program access to information about the error. Here are the most important
public members Err exposes:
Err.Number |
Long integer indicating the error code number. This is pretty much
useless except where the vendor of the product that generated this error was
too lazy to provide a useful description. |
Err.Source |
Generally used to tell your handler what component or code element is
responsible for generating the error. With custom errors, you might want to
set this to "ModuleName.MethodName()". |
Err.Description |
The all-important, human-readable description. The point of this is so
you're not left scratching your head wondering "what the heck does '-10021627'
mean?" |
Err.Clear() |
Allows you to sweep the error under the rug, so to speak. |
Err.Raise(Number, [Source], [Description], [HelpFile], [HelpContext])
|
Allows you to "raise", or invoke, your own run-time error. Number can be
vbObjectError + CustomErrorCode if you're not raising one of the
standard ones. Be sure to provide a source and description. |
The .HelpFile and .HelpContext properties, not listed
above, can be used by your program to refer users to a relevant passage in some
help file. Few programs bother.
The nice thing about go-to error trapping is that it allows you to easily
enwrap a large chunk of code with your error handler with one single line of
code (On Error GoTo Label). The resume approach really requires
you to either include error handling code after every line or to take a blind
leap of faith that a given line will either never encounter an error or that it
won't matter. As a general rule, use On Error Resume Next only for short blocks
of code.
One of the interesting nuances of the VB run-time error mechanism is that it
propagates errors "backwards". To illustrate what this means, consider the
following code:
Sub A
On Error Resume Next
Call B
MsgBox Err.Description
End Sub
Sub B
Call C
End Sub
Sub C
X = 1 / 0 'Division by zero
End Sub
A calls B, which in turn calls C. Since C
will cause a division-by-zero run-time error and itself has no error handler, VB
will effectively leave C and go back to B. But B
doesn't have an error handler, either, so VB leaves B to go back to
A. Fortunately, A does have an error handler. If it didn't, A
would also immediately exit and control would go back to whatever called it. If
there's nothing left up this "calling stack", your program will courteously
commit suicide.
You can use this "backward propagation" property of VB's error mechanism to
your advantage in many ways. First, you can enwrap a block of code by putting it
in its own subroutine and putting your error handler in the code that calls that
subroutine. In this case, any run-time error in that subroutine will propagate
back to your calling code. Second, you can add value to an error message by
adding more context information. You might use code like the following, for
instance:
Sub A
On Error GoTo AwShoot
Call B
Exit Sub
AwShoot:
Err.Raise vbObjectError, "MyModule.A(): " & Err.Source, _
"Unexpected failure in A: " & Err.Description
End Sub
Sub B
On Error GoTo AwShoot
Err.Raise vbObjectError, "My left nostril", "Stabbing pain"
Exit Sub
AwShoot:
Err.Raise vbObjectError, "MyModule.B(): " & Err.Source, _
"Couldn't complete B: " & Err.Description
End Sub
Calling A will result in an error whose source is "MyModule.A():
MyModule.B(): My left nostril" and whose description is "Unexpected
failure in A: Couldn't complete B: Stabbing pain". Having the extra
"source" information probably won't help your end-users. But then, your end
users probably won't care about the source of the problem, any way. But as the
person who gets to fix it, this will be invaluable to you. The extra description
information might actually help your end users, but it too will be invaluable to
you. Note, incidentally, that calling Err.Raise() in your error handler
will not cause the error to be thrown back to itself, again. With the go-to
method of error handling, as soon as the error is raised and before control is
passed to your error handler (right after the AwShoot: line label), the
error handler for your routine is automatically switched off. If you want to
trap errors in your error handler code, you'll have to reset the error handler
with another On Error Resume Next or On Error GoTo
Some_Other_Label line in your handler.
For those times you use the resume approach, be aware that calling On
Error GoTo 0 not only disables error handling in the current routine, it
also clears the current error properties, including the description. If you want
to add your own custom error message before propagating the error back up the
call stack in a fashion like that above, you'll need to grab the properties from
Err, first. Here's a simple way to do it:
Sub Doodad
On Error Resume Next
X = 1 / 0
If Err.Number <> 0 Then
'Dump Err properties into an array
EP = Array(Err.Number, Err.Source, _
Err.Description, Err.HelpFile, Err.HelpContext)
'Re-enable VB's own error handler
On Error GoTo 0
'Propagate error back up the call stack with my two cents added
Err.Raise EP(0), "MyModule.Doodad(): ", EP(1), _
"Something bad happened: " & EP(2), EP(3), EP(4)
End If
On Error GoTo 0
Exit Sub
End Sub
Finally, let me strongly urge you to have your programs raise errors as a
natural matter of course. Functions often return special values like 0, "", Null
and so on to indicate that an error has occurred. Instead of doing this and
requiring your users (other programmers) to figure out your special error
representations and to make non-standard error handlers for them, try calling
Err.Raise(). If your users don't realize that an invocation of your
code may cause an error, the first case may leave them with a difficult mystery
to solve, whereas the second case will leave little doubt about the real cause.
Plus, they'll be able to make their code more readable and consistent with
best-practice standards.
In summary, VB's run-time error trapping and handling mechanism allows your
code to take control of how errors are managed. This can be used to allow your
programs to more gracefully end, to let your programs continue running despite
certain kinds of problems, to give developers better clues about the causes of
bugs in their code, and more. There are two basic approaches: "resume" and
"go-to". VB's built-in Err object holds the information you need to find out
where the error occurred and what its nature is and allows you to clear or raise
errors of your own.