This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links. .gif)
Error Handling the VB.NET Way: Living with Exceptions
Gary Cornell and Jonathan Morrison
In Debbie Cooper's article this month, she shows how to use API functions to
retrieve error numbers and error messages in VB6. Here, Gary Cornell and
Jonathan Morrison illustrate how VB.NET's new structured error handling is going
to change how we code. This article is adapted from their forthcoming book, Programming
VB.NET: A Guide for Experienced Programmers (Apress, ISBN 1893115992).
Unfortunately, bad things can happen to good programs: A network connection
might go down, the printer might run out of paper, and so forth. As the
programmer, it's not your fault when these things happen, but it's not
the user's fault when the network goes down, either. At the very least, your
program must do the following:
- Log or in some way notify the user of the problem
- Allow the user to save his or her work if appropriate
- Allow users to gracefully exit the program if they think it necessary
Of course, this is easier said than done. The code that tried to open the
network connection typically isn't going to be attached to the objects whose
state you need to maintain, and you'll usually also need some way to transfer
control as well as inform other objects what happened so they can deal
with the situation too.
Other programming languages have had what's usually called structured
exception handling for years, but not VB—until VB.NET. No, don't worry,
structured exception handling won't break your On Error syntax. However, we
honestly think you'd be foolish to continue using it for new programs.
Error checking vs. exception handling
Traditional error checking (certainly that used in current versions
of VB and in traditional COM or Windows programming) is done by first checking
the return value of a given function and then doing something based upon that
value. This usually involves the equivalent of implementing a giant switch
statement that checks the value returned by the function. Unfortunately, the
return value itself sometimes seems to be random. Sometimes a "1" is
good, sometimes it isn't. Sometimes a "0" means success, sometimes
failure. And in the sample code given here, the value returned can seem truly
random:
Select Case ErrorNumber
Case 57
MsgBox "Your printer may be off-line."
Case 68
MsgBox "Is there a printer available?"
…
Case Else
' oops
End Select
Although this kind of code gets the job done, it's fair to say that it's hard
to read and harder to maintain. We think it's also fair to say there's a lot of
room for programmer error in this scheme. For instance, suppose that you made a
mistake with one of the error values or, as is all too common, forgot to check
all of the possible return values of the error function. Plus, it's just a pain
to write the same error-checking code every time you use a Windows API function.
In particular, while there are times when you'll have to check the return value
of a function regardless of what type of error-handling scheme you're using, you
don't want to do it everywhere.
Here's a good analogy to help you understand the difference between error
checking and structured exception handling. Think of structured exception
handling as the way traffic control works: Police patrol the streets looking for
vehicles that break the law. If a police officer sees you breaking the law, he
or she will pull you over and give you a ticket. The process of the police
officer giving you a ticket is analogous to structured exception-handling code
throwing an exception in an application—most of the time, things proceed
pretty efficiently and you aren't bothered. Now imagine the case for error
checking by looking at the return value of a function. This would be analogous
to your having to stop every time you saw a police officer and ask him or
her if you've broken the law (or worse yet, "Did I break Law 1? No? What
about Law 2?…"). Now, even though this scenario might seem a little bit
contrived, if you think it through you should start to see the benefit of using
structured exception handling over error checking alone. For example, one key
benefit is efficiency: Exception-handling code costs you less time in writing,
less time in maintenance, and often less time in executing!
First steps in exception handling
Before we start writing the code that shows you some exception
handlers at work, you have to keep in mind that when you use structured
exception handling you're providing an alternative path for your code that will
be executed automatically when something bad happens. More precisely,
VB.NET allows you to create in every piece of code inside a method an alternate
path for code when code is unable to complete its work in the normal way. If
this happens, VB.NET automatically creates an object that encapsulates the error
information, and the built-in exception-handling mechanism begins its search for
a handler that can deal with that particular object (error condition). It's
important to keep in mind that what we're describing isn't a bunch of
"GoTos" (see the sidebar) that make for spaghetti code—it's more
like the service road that runs parallel to the main highway.
Next, keep in mind that, in a way, this "service road" is the dream
of all drivers who are stuck in traffic. It's a smart service road—if
something goes wrong, you'll automatically be shunted to the exception-handling
code sequence. (Well, you will be if you wrote the code for the exception
handler.) Once you're on the "service road," the code in the exception
handler can deal with the problem or, optionally, let it "bubble up."
This means the exception handler passes the exception object on to the code that
called the code that caused the exceptional condition.
The actual mechanism for doing this in VB.NET is called a Try-Catch block.
Here's an example: Suppose you build a Console application called ProcesssFile.
The user is supposed to use this application from the command line by typing
something like this:
ProcessFile nameoFile
As users are prone to do, he or she can do one of many annoying things to
your nice program:
- Forget to give you a filename
- Give you a non-existent file
- Ask you to work with a file that's locked for this operation
- And so on and so forth...
We want to write the code that assumes we have a real user in place who will
do his or her best to frustrate us. For this example, we'll use the nifty .NET
Framework method System.Environment.GetCommandLineArgs(), which returns an array
of strings that are the command line arguments. The 0th entry in this array is
the path name of the application, but index 1 corresponds to the name of the
file we want to use. Here's an example of the simple Try-Catch block in a VB.NET
application that could be part of the ProcessFile application:
Module Exception1
Sub Main()
Dim args() As String
Dim argument As String
args = Environment.GetCommandLineArgs()
Try
ProcessFile(args(1))
Catch
Console.WriteLine("ERROR")
End Try
Console.WriteLine("Press enter to end")
Console.ReadLine()
End Sub
Sub ProcessFile(ByVal fName As String)
'process file code goes here
Console.WriteLine("Am processing " & fName)
End Sub
End Module
The code in the Try section of the Try-Catch block is assumed to be
"good" code—in this case, a call to ProcessFile. The code in the
Catch section of the Try-Catch block is there because, well, users are users. In
this code snippet, if the user forgot to enter a filename, the code would try to
reference the name of the file and cause an IndexOutOfRangeException. This would
cause the flow to move down the alternate pathway (the Catch block), which
simply prints out ERROR in the command window.
The next step is not only to catch the exception but also to analyze it, and
we do this by modifying the key catch line to read:
Catch e As Exception
Now the exception object referenced by "e" contains an incredible
amount of information. For example, change the code in the Catch clause to read:
Catch e As Exception
Console.WriteLine(e)
in order to take advantage of the built-in ToString method of the exception
object e, and you'll see the following, which, of course, says it all if the
user forgot to enter a filename!
System.IndexOutOfRangeException: An exception of type
System.IndexOutOfRangeException was thrown.
at Exception_1.Exception1.Main() in C:\Documents and
Settings\x20\My Documents\Visual Studio Projects
\ConsoleApplication14\Exception1.vb:line 6
(Not that we recommend printing this information out for users—unless you
want to scare them, but that's another issue.)
Finally, while reading this code, we hope you're thinking ahead. Suppose the
user supplied a filename but the ProcessFile method couldn't process it. Then
what? Well, the Catch clause can be made more sophisticated. Furthermore, the
ProcessFile method can even throw an exception object back to Main that
encapsulated what went wrong in its work. Oh, and yes, you're even allowed to
have multiple Catch clauses, as follows:
Sub Main()
Dim args(), argument As String
args = Environment.GetCommandLineArgs()
Try
ProcessFile(args(1))
Catch indexProblem As IndexOutOfRangeException
Console.WriteLine("ERROR - No file name _
supplied")
Catch ioProblem As System.IO.IOException
Console.WriteLine("ERROR - can't process file _
named " & args(1))
End Try
Console.WriteLine("Press enter to end")
Console.ReadLine()
End Sub
Note that no other code that you wrote in any other Catch blocks inside the
same Try statement will be processed once a match is made.
Some tips on using exceptions
Exceptions are cool, but it's all too easy to overuse them—at the
cost of performance. Here are three tips on using exceptions, but they all come
down to variations on the rule that exceptions are supposed to be exceptional.
In other words, remember that exception handling isn't supposed to replace
testing for the obvious. You don't, for example, use exceptions to test for EOF
conditions. Similarly:
- Don't micromanage exceptions by wrapping every possible statement in a
Try-Catch block. It's usually better to wrap the whole action in a single Try
statement than to have multiple Try statements.
- Don't squelch exceptions by writing code like this without a very good
reason:
Catch e as Exception
- This is the equivalent of blindly using On Error Resume Next in older VB
code and is bad for the same reasons. If an exception happens, either handle
it or propagate it.
Which leads to the final rule—what we like to call "the good
fellowship rule" for exceptions:
- If you don't handle an exceptional condition completely and need to
rethrow an exception to the calling code, add enough information (or create a
new exception class) so that the code you're communicating with knows exactly
what happened and what you did to (try to) fix it.
With that, we'll close, Microsoft-style, with a "call to action":
Get exceptional.
Download
>CORNELL.TXT
Sidebar: The Infamous "GoTo"
Well-known Dutch computer scientist Edsger W. Dijkstra (author of several
college texts) opined against "GoTos" in his famous and often-cited
classic article "Go To Statement Considered Harmful" in 1968. You can
read the original on the ACM Web site at www.acm.org/classics/oct95/. And
here's the reference if you ever need to cite it yourself: Communications of
the ACM, Vol. 11, No. 3, March 1968, pp. 147-148.
Dijkstra was the 1972 ACM Turing award winner (do a Google search or refer to
www.turing.org.uk if you're not familiar
with Alan Turing and the Turing Test). Dijkstra's ACM citation explains how, in
the late 1950s, he was one of the principal contributors to the development of
the ALGOL and that he's "one of the principal exponents of the science and
art of programming languages in general, and has greatly contributed to our
understanding of their structure, representation, and implementation. His 15
years of publications extend from theoretical articles on graph theory to basic
manuals, expository texts, and philosophical contemplations in the field of
programming languages."
(You can read a slightly more extensive bio at http://henson.cc.kzoo.edu/~k98mn01/dijkstra.html,
where you'll also find bios of other computer science pioneers.)
Another giant figure in computer science is Donald Knuth, and his rebuttal
("Structured Programming with GOTO Statements," Computing Surveys,
Volume 6, 1974, pp. 261-301) to Dijkstra's comments on GoTos is almost as famous
as the original document. By the way, Professor Knuth, the inventor of the TEX
typesetting language and author of the classic "Art of Programming"
series of books on algorithms, has a home page at www-cs-faculty.stanford.edu/~knuth/,
and there's a funny cartoon about him and his alleged book, Donald Knuth's
Big Dummies Guide to Visual Basic, at www.ibiblio.org/Dave/Dr-Fun/df200002/df20000210.jpg.
To find out more about Visual Basic Developer and Pinnacle Publishing, visit their website at
http://www.pinpub.com/
Note: This is not a Microsoft Corporation website. Microsoft is not responsible for its content.
This article is reproduced from the June 2001 issue of Visual Basic Developer. Copyright 2001, by Pinnacle Publishing, Inc., unless otherwise noted. All rights are reserved. Visual Basic Developer is an independently produced publication of Pinnacle Publishing, Inc. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of Pinnacle Publishing, Inc. To contact Pinnacle Publishing, Inc., please call 1-800-788-1900.