Chapter 9. Error Processing

Debugging and error processing are two of the most essential programming activities you will ever perform. There are three absolutes in life: death, taxes, and software bugs. Even in a relatively bug-free application, there is every reason to believe that a user will just mess things up royally. As a programmer, your job is to be the guardian of the user’s data as managed by the application, and to keep it safe, even from the user’s own negligence (or malfeasance) and also from your own source code.

I recently spoke with a developer from a large software company headquartered in Redmond, Washington; you might know the company. This developer told me that in any given application developed by this company, more than 50 percent of the code is dedicated to dealing with errors, bad data, system exceptions, and failures. Certainly, all this additional code slows down each application and adds a lot of overhead to what is already called “bloatware.” But in an age of hackers and data entry mistakes, such error management is an absolute must.

Testing—although not a topic covered in this book—goes hand-in-hand with error management. Often, the report of an error will lead to a bout of testing, but it should really be the other way around: Testing should lead to the discovery of errors. When I first started work on this chapter, the daily news was reporting that NASA’s Mars Global Surveyor, in orbit around the red planet, had captured images of the Beagle 2, a land-based research craft that crashed into the Martian surface in 2003. An assessment of the Beagle 2’s failure pinpointed many areas of concern, with a major issue being inadequate testing:

This led to an attenuated testing programme to meet the cost and schedule constraints, thus inevitably increasing technical risk. (From Beagle 2 ESA/UK Commission of Inquiry Report, April 5, 2004, Page 4)

Look at all those big words. Boy, the Europeans sure have a way with language. Perhaps a direct word-for-word translation into American English will make it clear what the commission was trying to convey:

They didn’t test it enough, and probably goofed it all up.

The Nature of Errors in Visual Basic

There are three major categories of errors that you will deal with in your Visual Basic applications.

  1. Compile-Time Errors. Some errors are so blatant that Visual Basic will refuse to compile your application. Generally, such errors are due to simple syntax issues that can be corrected with a few keystrokes. But you can also enable features in your program that will increase the number of errors recognized by the compiler. For instance, if you set Option Strict to On in your application or source code files, implicit narrowing conversions will generate compile-time errors.

    Image

    Visual Studio 2005 includes new features that help you locate and resolve compile-time errors. Such errors are marked with a “blue squiggle” below the offending syntax. Some errors also prompt Visual Studio to display corrective options through a pop-up window, as shown in Figure 9-1.

    Figure 9-1. Error correction options for a narrowing conversion

    Image

  2. Run-Time Errors. Run-time errors occur when a combination of data and code causes an invalid condition in what otherwise appears to be valid code. Such errors frequently occur when a user enters incorrect data into the application, but your own code can also generate run-time errors. Adequate checking of all incoming data will greatly reduce this class of errors. Consider the following block of code:

    Image

    This code looks pretty reasonable, and in most cases, it is. It prompts the user for a number, converts valid numbers to integer format, and returns the result. The IsNumeric function will weed out any invalid non-numeric entries. Calling this function will, in fact, return valid integers for entered numeric values, and zero for invalid entries.

    But what happens when a fascist dictator tries to use this code? As history has shown, a fascist dictator will enter a value such as “342304923940234.” Because it’s a valid number, it will pass the IsNumeric test with flying colors, but since it exceeds the size of the Integer data type, it will generate the dreaded run-time error shown in Figure 9-2.

    Figure 9-2. An error message only a fascist dictator could love

    Image

    Without additional error-handling code or checks for valid data limits, the GetNumber routine generates this run-time error, and then causes the entire program to abort. Between committing war crimes and entering invalid numeric values, there seems to be no end to the evil that fascist dictators will do.

  3. Logic Errors. Logic errors are the third, and the most insidious, type of error. They are caused by you, the programmer; you can’t blame the user on this one. From process-flow issues to incorrect calculations, logic errors are the bane of software development, and result in more required debugging time than the other two types of errors combined.

    Logic errors are too personal and too varied to directly address in this book. Many logic errors can be forced out of your code by adding sufficient checks for invalid data, and by adequately testing your application under a variety of conditions and circumstances.

You won’t have that much difficulty dealing with compile-time errors. A general understanding of Visual Basic and .NET programming concepts, and a regular use of the tools included with Visual Studio 2005, will help you quickly locate and eliminate them.

The bigger issue is: What do you do with run-time errors? Even if you check all possible data and external resource conditions, it’s impossible to prevent all run-time errors. You never know when a network connection will suddenly go down, or the user will trip over the printer cable, or a scratch on a DVD will generate data corruption. Any time you deal with resources that exist outside of your source code, you are taking a chance that run-time errors will occur.

Figure 9-2 showed you what Visual Basic does when it encounters a run-time error: It displays to the user a generic error dialog, and offers a chance to ignore the error (possible corruption of any unsaved data) or exit the program immediately (complete loss of any unsaved data).

Although both of these user actions leave much to the imagination, they don’t instill consumer confidence in your coding skills. Trust me on this: The user will blame you for any errors generated by your application, even if the true problem was far removed from your code.

Fortunately, Visual Basic includes three tools to help you deal completely with run-time errors, if and when they occur. These three Visual Basic features—unstructured error handling, structured error handling, and unhandled error handling—can all be used in any Visual Basic application to protect the user’s data—and the user—from unwanted errors.

Unstructured Error Handling

Unstructured error handling has been a part of Visual Basic since it first debuted in the early 1990s. It’s simple to use, catches all possible errors in a block of code, and can be enabled or disabled as needed. By default, methods and property procedures include no error handling at all, so you must add error-handling code—unstructured or structured—to every routine where you feel it is needed.

The idea behind unstructured error handling is pretty basic. You simply add a line in your code that says, “If any errors occur at all, temporarily jump down to this other section of my procedure where I have special code to deal with it.” This “other section” is called the error handler.

Image

The On Error statement enables or disables error handling in the routine. When an error occurs, Visual Basic places the details of that error in a global Err object. This object stores the numeric error code, a text description of the error, related online help details, and other error-specific values. I’ll list the details a little later.

You can include as many On Error statements in your code as you want, and each one could direct errant code to a different label. You could have one error handler for network errors, one for file errors, one for calculation errors, and so on. Or, you could have one big error handler that uses If...Then...Else statements to examine the error condition stored in the global Err object.

Image

Specific error numbers for common errors can be found in the online documentation for Visual Studio, but it is this dependence on hard-coded numbers that makes unstructured error handling less popular today than it was before .NET. Still, you are under no obligation to treat errors differently based on the type of error. As long as you can recover from error conditions reliably, it doesn’t always matter what the cause of the error was. Many times, if I have enabled error handling where it’s not the end of the world if the procedure reaches the end in an error-free matter, I simply report the error details to the user, and skip the errant line.

Image

This block of code reports the error, and then uses the Resume Next statement (a variation of the standard Resume statement) to return to the code line immediately following the one that caused the error. Another option uses Resume some_other_label, which returns control to some specific named area of the code.

Disabling Error Handling

Using On Error GoTo enables a specific error handler. Although you can use another On Error GoTo statement to redirect errors to another error handler in your procedure, a maximum of one error handler can be in effect at any moment. Once you have enabled an error handler, it stays in effect until the procedure ends, you redirect errors to another handler, or you specifically turn off error handling in the routine. To take this last route, issue the following statement:

On Error GoTo 0

Ignoring Errors

Your error handler doesn’t have to do anything special. Consider this error-handling block.

ErrorHandler:
   Resume Next

When an error occurs, this handler immediately returns control to the line just following the one that generated the error. Visual Basic includes a shortcut for this action.

On Error Resume Next

By issuing the On Error Resume Next statement, all errors will populate the Err object (as is done for all errors, no matter how they are handled), and then skip the line generating the error. The user will not be informed of the error, and will continue to use the application in an ignorance-is-bliss stupor.

Structured Error Handling

Unstructured error handling was the only method of error handling available in Visual Basic before .NET. Although it was simple to use, it didn’t fulfill the hype that surrounded the announcement that the 2002 release of Visual Basic .NET would be an object-oriented programming system. Therefore, Microsoft also added structured error handling to the language, a method that uses standard objects to communicate errors, and error-handling code that is more tightly integrated with the code it monitors.

This form of error processing uses a multi-line Try...Catch...Finally statement to catch and handle errors.

Image

The Try Clause

Try statements are designed to monitor smaller chunks of code. Although you could put all the source code for your procedure within the Try block, it’s more common to put only the statements that are likely to generate errors within that section.

Image

“Safe” statements can remain outside of the Try portion of the Try...End Try statement. Exactly what constitutes a “safe” programming statement is topic of much debate, but there are two types of statements that are generally unsafe: (1) those statements that interact with external systems, such as disk files, network or hardware resources, or even large blocks of memory; and (2) those statements that could cause a variable or expression to exceed the designed limits of the data type for that variable or expression.

The Catch Clause

The Catch clause defines an error handler. As with unstructured error handling, you can include one global error handler in a Try statement, or you can include multiple handlers for different types of errors. Each handler includes its own Catch keyword.

Catch ex As ErrorClass

The ex identifier provides a variable name for the active error object that you can use within the Catch section. You can give it any name you wish; it can vary from Catch clause to Catch clause, but it doesn’t have to.

ErrorClass identifies an exception class, a special class specifically designed to convey error information. The most generic exception class is System.Exception; other more specific exception classes derive from System.Exception. Because Try...End Try implements “object-oriented error processing,” all the errors must be stored as objects. The .NET Framework includes many pre-defined exception classes already derived from System.Exception that you can use in your application. For instance, System.DivideByZeroException catches any errors that (obviously) stem from dividing a number by zero.

Image

When an error occurs, your code tests the exception against each Catch clause until it finds a matching class. The Catch clauses are examined in order from top to bottom, so make sure you put the most general one last; if you put System.Exception first, no other Catch clauses in that Try block will ever trigger because every exception matches System.Exception. How many Catch clauses you include, or which exceptions they monitor, is up to you. If you leave out all Catch clauses completely, it will act somewhat like an On Error Resume Next statement, although if an error does occur, all remaining statements in the Try block will be skipped. Execution continues with the Finally block, and then with the code following the entire Try statement.

The Finally Clause

The Finally clause represents the “do this or die” part of your Try block. If an error occurs in your Try statement, the code in the Finally section will always be processed after the relevant Catch clause is complete. If no error occurs, the Finally block will still be processed before leaving the Try statement. If you issue a Return statement somewhere in your Try statement, the Finally block will still be processed before leaving the routine. (This is getting monotonous.) If you use the Exit Try statement to exit the Try block early, the Finally block is still executed. If, while your Try block is being processed, your boss announces that a free catered lunch is starting immediately in the big meeting room and everyone is welcome, the Finally code will also be processed, but you might not be there to see it.

Finally clauses are generally optional, so you need to include it only when you need it. The only time that Finally clauses are required is when you omit all Catch clauses in a Try statement.

Unhandled Errors

I showed you earlier in the chapter how unhandled errors can lead to data corruption, crashed applications, and spiraling out-of-control congressional spending. All good programmers understand how important error-handling code is, and make the extra effort of including either structured or unstructured error-handling code. Yet there are times when I, even I, as a programmer, think, “Oh, this procedure isn’t doing anything that could generate errors. I’ll just leave out the error-handling code and save some typing time.” And then it strikes, seemingly without warning: an unhandled error. Crash! Burn! Another chunk of user data confined to the bit bucket of life.

Normally, all unhandled errors “bubble up” the call stack, looking for a procedure that includes error-handling code. For instance, consider this code.

Image

When the error occurs in Level3, the application looks for an active error handler in that procedure, but finds nothing. So it immediately exits Level3 and returns to Level2, where it looks again for an active error handler. Such a search will, sadly, be fruitless. Heartbroken, the code leaves Level2 and moves back to Level1, continuing its search for a reasonable error handler. This time it finds one. Processing immediately jumps down to the ErrorHandler block and executes the code in that section.

If Level1 didn’t have an error handler, and no code farther up the stack included an error handler, the user would see the Error Message Window of Misery (refer to Figure 9-2), followed by the Dead Program of Disappointment.

Fortunately, Visual Basic does support a “catch all” error handler that traps such unmanaged exceptions and lets you do something about them. This feature works only if you have the Enable application framework field selected on the Application tab of the project properties. To access the code template for the global error handler, click the View Application Events button on that same project properties tab. Select “(MyApplication Events)” from the Class Name drop-down list above the source code window, and then select “UnhandledException” from the Method Name list. The following procedure appears in the code window.

Image

Add your special global error-handling code to this routine. The e event argument includes an Exception member that provides access to the details of the error via a System.Exception object. The e.ExitApplication member is a Boolean property that you can modify either to continue or to exit the application. By default, it’s set to True, so modify it if you want to keep the program running.

Even when the program does stay running, you will lose the active event path that triggered the error. If the error stemmed from a click on some button by the user, that entire Click event, and all of its called methods, will be abandoned immediately, and the program will wait for new input from the user.

Managing Errors

In addition to simply watching for them and screaming “Error!” there are a few other things you should know about error management in Visual Basic programs.

Generating Errors

Believe it or not, there are times when you might want to generate run-time errors in your code. In fact, many of the run-time errors you encounter in your code occur because Microsoft wrote code in the Framework Class Libraries that specifically generates errors. This is by design.

Let’s say that you had a class property that was to only accept percentage values from 0 to 100, but as an Integer data type.

Image

There’s nothing grammatically wrong with this code, but it will not stop anyone from setting the stored percent value to either 847 or –847, both outside the desired range. You can add an If statement to the Set accessor to reject invalid data, but properties don’t provide a way to return a failed status code. The only way to inform the calling code of a problem is to generate an exception.

Image

Now, attempts to set the InEffectPercent property to a value outside the 0-to-100 range will generate an error, an error that can be caught by On Error or Try...Catch error handlers. The Throw statement accepts a System.Exception (or derived) object as its argument, and sends that exception object up the call stack on a quest for an error handler.

Similar to the Throw statement is the Err.Raise method. It lets you generate errors using a number-based error system more familiar to Visual Basic 6.0 and earlier environments. Personally, I recommend that you use the Throw statement, even if you employ unstructured error handling elsewhere in your code.

Mixing Error-Handling Methods

You are free to mix both unstructured and structured error-handling methods broadly in your application, but a single procedure or method may only use one of these methods. That is, you may not use both On Error and Try...Catch...Finally in the same routine. A routine that uses On Error may call another routine that uses Try...Catch...Finally with no problems.

Now you may be thinking to yourself, “Self, I can easily see times when I would want to use unstructured error handling, and other times when I would opt for the more structured approach.” It all sounds very reasonable, but let me warn you in advance that there are error-handling zealots out there who will ridicule you for decades if you ever use an On Error statement in your code. For these programmers, “object-oriented purity” is essential, and any code that uses non-object methods to achieve what could be done through an OOP approach must be destroyed.

Warning

I’m about to use a word that I forbid my nine-year-old son from using. If you have tender ears, cover them now, though it won’t protect you from seeing the word on the printed page.

Rejecting the On Error statement like this is just plain stupid. As you may remember from earlier chapters, everything in your .NET application is object-oriented, because all the code appears in the context of an object. If you are using unstructured error handling, you can still get to the relevant exception object through the Err.GetException() method, so it’s not really an issue of objects. Determining when to use structured or unstructured error handling is no different from deciding to use C# or Visual Basic to write your applications. For most applications, the choice is irrelevant. There may be some esoteric features that one language has that may steer you in that direction (such as optional method arguments in Visual Basic), but the other 99.9% of the features are pretty much identical.

The same is true of error-handling methods. There may be times when one is just plain better than the other. For instance, consider the following code that calls three methods, none of which include their own error handler.

Image

Clearly I don’t care if an error occurs in one of the routines or not. If an error causes an early exit from RefreshPart1, the next routine, RefreshPart2, will still be called, and so on. I often need more diligent error-checking code than this, but in low-impact code, this is sufficient. To accomplish this same thing using structured error handling would be a little more involved.

Image

That’s a lot of extra code for the same functionality. If you’re an On Error statement hater, then by all means, use the second block of code. But if you are a more reasonable programmer, the type of programmer who would read a book like this, then use each method as it fits into your coding design.

The System.Exception Class

The System.Exception class is the base class for all structured exceptions. When an error occurs, you can examine its members to determine the exact nature of the error. You also use this class (or one of its derived classes) to build your own custom exception in anticipation of using the Throw statement. Table 9-1 lists the members of this object.

Table 9-1. Members of the System.Exception Class

Image

Classes derived from System.Exception may include additional properties that provide additional detail for a specific error type.

The Err Object

The Err object provides access to the most recent error through its various members. Anytime an error occurs, Visual Basic documents the details of the error in this object’s members. It’s often accessed within an unstructured error handler to reference or display the details of the error. Table 9-2 lists the members of this object.

Table 9-2. Members of the Err Object

Image

The Debug Object

Visual Basic 6.0 (and earlier) included a handy tool that would quickly output debug information from your program, displaying such output in the “Immediate Window” of the Visual Basic development environment.

Debug.Print "Reached point G in code"

The .NET version of Visual Basic enhances the Debug object with more features, and a slight change in syntax. The Print method is replaced with WriteLine; a separate Write method outputs text without a final carriage return.

Debug.WriteLine("Reached point G in code")

Everything you output using the WriteLine (or similar) method goes to a series of “listeners” attached to the Debug object. You can add your own listeners, including output to a work file. But the Debug object is really only used when debugging your program. Once you compile a final release, none of the Debug-related features work anymore, by design.

If you wish to log status data from a released application, consider using the My.Application.Log object instead (or My.Log in ASP.NET programs). Similar to the Debug object, the Log object sends its output to any number of registered listeners. By default, all output goes to the standard debug output (just like the Debug object) and to a log file created specifically for your application’s assembly. See the online help for the My.Application.Log Object for information on configuring this object to meet your needs.

Other Visual Basic Error Features

The Visual Basic language includes a few other error-specific statements and features that you may find useful:

  • ErrorToString Function. This method returns the error message associated with a numeric system error code. For instance, ErrorToString(10) returns “This array is fixed or temporarily locked.” It is useful only with older unstructured error codes.
  • IsError Function. When you supply an object argument to this function, it returns True if the object is a System.Exception (or derived) object.

Summary

The best program in the world would never generate errors, I guess. But come on, it’s not reality. If a multi-million dollar Mars probe is going to crash on a planet millions of miles away even after years of advanced engineering, then my customer-tracking application for a local video rental shop is certainly going to have a bug or two. But you can mitigate the impact of these bugs using the error-management features included with Visual Basic.

Project

This chapter’s project code will be somewhat brief. Error-handling code will appear throughout the entire application, but we’ll add it in little by little as we craft the project. For now, let’s just focus on the central error-handling routines that will take some basic action when an error occurs anywhere in the program.

General Error Handler

As important and precise as error handling needs to be, the typical business application will not encounter a large variety of error types. Applications like the Library Project are mainly vulnerable to three types of errors: (1) data entry errors; (2) errors that occur when reading data from, or writing data to, a database table; and (3) errors related to printing. Sure, there may be numeric overflow errors or other errors related to in-use data, but it’s mostly interactions with external resources, such as the database, that concern us.

Because of the limited types of errors occurring in the application, it’s possible to write a generic routine that informs the user of the error in a consistent manner. Each time a run-time error occurs, we will call this central routine, just to let the user know what’s going on. The code block where the error occurred can then decide whether to take any special compensating action, or continue on as if no error occurred.

Project Access

Load the “Chapter 9 (Before) Code” project, either through the New Project templates, or by accessing the project directly from the installation directory. To see the code in its final form, load “Chapter 9 (After) Code” instead.

In the project, open the General.vb class file, and add the following code as a new method to Module General.

Insert Snippet

Insert Chapter 9, Snippet Item 1.

Image

Not much to that code, is there? So, here’s how it works. When you encounter an error in some routine, the in-effect error handler calls the central GeneralError method.

Image

You can use it with structured errors as well.

Image

The purpose of the GeneralError global method is simple: Communicate to the user that an error occurred, and then move on. It’s meant to be simple, and it is simple. You could enhance the routine with some additional features. Logging of the error out to a file (or any other active log listener) might assist you later if you needed to examine application-generated errors. Add the following code to the routine, just after the MsgBox command, to record the exception.

Insert Snippet

Insert Chapter 9, Snippet Item 2.

My.Application.Log.WriteException(theError)

Of course, if an error occurs while writing to the log, that would be a big problem, so add one more line to the start of the GeneralError routine.

Insert Snippet

Insert Chapter 9, Snippet Item 3.

On Error Resume Next

Unhandled Error Capture

As I mentioned earlier, it’s a good idea to include a global error handler in your code, in case some error gets past your defenses. To include this code, display all files in the Solution Explorer using the “Show All Files” button, open the ApplicationEvents.vb file, and add the following code to the MyApplication class.

Insert Snippet

Insert Chapter 9, Snippet Item 4.

Image

Because we already have the global GeneralError routine to log our errors, we might as well take advantage of it here.

That’s it for errors. In the next chapter, which covers database interactions, we’ll make frequent use of this error-handling code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.251.163