Chapter 12

Debugging

by Keith Brophy and Timothy Koets


CONTENTS

If you have been a programmer for very long, you probably realize that writing programs is sometimes the easy part of software development. It's getting the bugs out that can be the real challenge! Judgment is needed to effectively sift through many hundreds or thousands of possible causes of a problem and hone in on a specific bug. At the same time, a great deal of logical, objective analysis must be applied to a debugging problem to scientifically pinpoint a bug and prove its existence. As a result, the process of bug-hunting often requires a special focus and concentration seldom encountered in other aspects of life. And, as anyone with experience debugging an application will quickly realize, the frustration involved can be enormous! The good news is that problem-solving techniques and top-notch tools can make this process much easier. The not-so-good news, however, is that such top-notch tools do not come with VBScript. Nevertheless, the tips and techniques suggested in this chapter will go a long way toward minimizing your debugging pitfalls.

Handling VBScript Errors

In order to effectively debug applications, you first must learn a bit about what errors are, what forms they take, and how to handle them when they occur. Once you know how to effectively handle them, you will be more effective in recognizing and preventing them. This first section gives you the background you need to learn about debugging techniques. The techniques themselves are presented in the next section of this chapter.

Unfortunately, VBScript offers little in the way of sophisticated debugging tools and error handling when your program is running. Most commercial language products, including Visual Basic 4.0, offer a development environment that is tremendously helpful when you're debugging programs. Later in this chapter, you'll learn more about those capabilities and what debugging tools VBScript lacks. But first, you should examine what VBScript does provide when errors occur. To do this, you should take a look at the various types of errors that can occur in a VBScript program.

Syntax Errors: Say What You Mean!

Errors come in a variety of shapes and sizes. The easiest ones to picture and correct are simple typing or language-usage errors in your VBScript code. For example, consider what would happen if you mistakenly type


Dimwit C

instead of

Dim C

in order to declare a variable named C. Because the word Dimwit is not part of the VBScript language or syntax, the first statement can't be processed. This kind of error commonly is called a syntax error.

Obviously, if you have a syntax error, your program is not going to work as intended. Fortunately, a program with a syntax error normally won't even run, and the problem can be spotted right away! Such stubborn behavior is fortunate, because the sooner you find a problem, the better. You'd probably rather have your program go "on strike" immediately than have the problem pop up when a user is interacting with your script or, worse yet, when your program gives your user an incorrect result that he's not even aware of!

So how does VBScript respond to a syntax error? Suppose that you have the statement


Dim c, c, c

This statement, too, contains a syntax error. This time, the error consists of an illegal duplicate definition instead of an incorrectly typed keyword. This statement is not legal according to the rules of VBScript, because a variable name can be used only once; here, it is defined three times in a row. When you attempt to load the page containing this program into the browser, you are greeted with the message shown in Figure 12.1.

Figure 12.1 : A syntax-checking script error message.

This particular error is identified as soon as the page is loaded in the browser, but other syntax errors might not be caught until after the Web page is loaded. Suppose that a certain code statement is carried out only after a specific button is clicked. In the script event-handler routine associated with that button, suppose that a series of other calculations precedes the statement


a = b / c

Now, assume that the variable c already has been computed before this statement and that the value stored in c varies from one calculation to another. This statement might work perfectly well for the first several times the button is clicked. If, however, the value of c ever turns out to be 0, this statement fails. Because the computer is unable to divide by 0, VBScript generates a message box similar to the one in Figure 12.1. This brings your program to a screeching halt.

NOTE
When you are presented with the runtime error message from the Internet Explorer browser, you have the option of enabling a checkbox to suppress notification of future runtime errors. If you enable the Ignore further script errors on this page checkbox, notification of future errors in other scripts on the page is suppressed. Loading of the specific script that caused the problem still is halted when errors occur.

These are just two examples of many possible errors that can be detected when VBScript tries to run your program. The errors shown in these examples are referred to as runtime errors. Hopefully, your user never sees any runtime errors. Ideally, you would write perfect, error-free code! However, given the complexity of programming, the odds of producing a perfect program are slim. Therefore, you must be able to thoroughly test your programs to remove all their problems before you turn them over to your users. You also can take steps when writing your code to make it more robust if a runtime error occurs during development.

NOTE
When a browser runs your VBScript code embedded in HTML, it does so by passing the VBScript statements to a separate component of software called the VBScript Interpreter. This interpreter checks and runs the VBScript code.

Unfortunately, no standard VBScript interactive development environment exists from Microsoft to make the debugging task easier. This makes the task of error-proofing your programs a considerable challenge. VBScript does provide some help in recovering from errors and pinning them down, however. You can write code that helps a program robustly continue after an error occurs. The On Error Resume Next statement serves this purpose. After an error occurs, a convenient source of information called the Err object is available for use in your code as well. With the Err object, you can write program logic that prints error information or takes a code path based on an analysis in code of what error occurred. These techniques for dealing with runtime errors are covered in "Using Simple Debugging Techniques" later in this chapter. But first, take a look at another type of error.

Semantic Errors: Mean What You Say!

By now, you might be feeling a little more at ease, comforted by the idea that there is some support in the VBScript language to help you handle errors. Don't get too comforted, though! First of all, VBScript support for runtime errors might help you handle them, but it won't help you prevent or eliminate them. Second, semantic errors can pose an even bigger problem than syntax errors. A semantic error is an error in meaning (that is, you fail to write the program to achieve the purpose you intended). Suppose that you want to add a 4 percent sales tax to the cost of an item. You provide the following code statement:


total = orig_price + orig_price * 4

4 is used here in place of .04. The result is that this incorrect statement doesn't add 4 percent to your total sale, but it does add four times the cost of your item to your total sale! This is clearly an error. As far as the VBScript Interpreter can tell, however, this statement is fine. VBScript doesn't know what a sales tax rate is. It obediently carries out the calculation you give it.

With a semantic error, the problem rests squarely on your shoulders. VBScript, or any other language for that matter, is not able to automatically highlight this kind of an error for you. Instead, after noticing an incorrect result, you must work backward until you hone in on the problem. Semantic problems do not directly cause runtime errors; they just lead to bad results. And although bad results might suggest that you have a problem, they don't tell you where it is. Often, you must trace through your program line by line, ensuring that each line is correct and produces valid results before proceeding to the next line.

Some languages offer support for this kind of tracing. In the case of VBScript, however, you must put together your own traces. Trace tactics that can be used to address errors are discussed in the following section, and there you'll get a closer look at runtime error-handling techniques you can use to tackle syntax errors. For now, recognize that it is not too important that you know the textbook description of semantic versus syntax errors. It is important, however, that you are aware of the techniques available for dealing with them. You also should realize that the most important tools for debugging-patience and persistence-can be provided only by you.

Using Simple Debugging Techniques

Now that you have a feel for the type of error support in VBScript, it's time to observe it in action. The program used to illustrate debugging principles throughout this chapter is the Pace-Pal program. Pace-Pal is a standard HTML Web page with embedded VBScript code. The program is shown in Figure 12.2.

Figure 12.2 : The Pace-Pal program.

Pace-Pal enables you to specify a distance in miles or kilometers and a time in minutes/seconds format. (Hours also can be provided optionally if you're willing to run that long!) With this information, a pace per mile can be calculated. If you run a 26.2 mile race (10k) in three hours and supply that information to Pace-Pal, for example, Pace-Pal calculates that you averaged 6:52-minute miles. This scenario is shown in Figure 12.2.

Pace-Pal does its job very nicely when it has perfect, well-mannered, never-make-a-mistake users. It runs into problems, however, when faced with the more typical user who occasionally makes mistakes. Specifically, Pace-Pal does a poor job of handling nonstandard input.

Pace-Pal can derive a 10k pace from a time of 37:12 faster than you can blink an eye. But if you accidentally type 37:12AA rather than 37:12 for the time, disaster strikes. Pace-Pal's code is not constructed to deal with a time in such a format. The code doesn't check the data integrity. Instead, it tries to process the data, causing the VBScript Interpreter to attempt the impossible with the current statement. An attempt is made to carry out a calculation based on character data. The poor VBScript Interpreter is left holding the bag, asked to carry out a statement that makes no sense and will lead to an incorrect result! Needless to say, the interpreter balks, tossing up the famed runtime error window. Figure 12.3 shows the runtime error window generated after Pace-Pal attempts to process a time of 37:12AA.

Figure 12.3 : Bad input data causes a runtime error in the Pace-Pal program.

VBScript is nice enough to clue you into the problem. The error message displayed tells you that the problem is related to an attempted type conversion that is illegal under the rules of VBScript. Unfortunately, VBScript doesn't tell you where this error occurred. And, even worse from the user's point of view, VBScript halts execution of the script because it detected problems there. If you go back and specify good input and click the Display Pace button, nothing happens. Until the page is reloaded, VBScript considers this a bad script and doesn't process any of it.

In certain cases, VBScript does provide you with the location of statements that contain errors. In many cases, fundamental flaws in language definition can be detected and pointed out with line-number information when a page is loaded. This is the case with the error shown in Figure 12.3. More subtle errors, or errors that only show up when there are certain data conditions, cannot be predetected. The 37:12AA bad-data-induced error falls into this category. Debugging then becomes considerably more complicated, because half the battle is simply determining which statements caused VBScript to balk.

Take a look at the code in Listing 12.1, for example. This is just one section of the rather lengthy Pace-Pal program. Even after you've gotten a hint that the culprit statement lurks somewhere in this subset of the code, does it easily jump out at you?

NOTE
The source file for the Pace-Pal program is located on the accompanying CD-ROM in the pace-pal.htm file.


Listing 12.1. Source code that contains an error.

  Function ConvertStringToTotalSeconds (ByVal sDuration)



  ' --------------------------------------------------------------------------

  ' Purpose: Takes HH:MM:SS format string and converts to total seconds

  ' --------------------------------------------------------------------------



Dim iPosition       ' Position of ":" seperator

     Dim vHours          ' Number of hours required

     Dim vMinutes        ' Number of minutes required

     Dim vSeconds        ' Number of seconds required



     ' Start working from right of string, parsing seconds

     sMode = "Seconds"



     ' Get leftmost time component

     iPosition = InStr(sDuration, ":")

     If iPosition = 0 Then



        ' No more time info, assume time info just in ss format

        vSeconds = sDuration



     Else ' More time info is in string



        ' Store first portion in hours for now, assume hh:mm:ss format

        vHours = Left(sDuration, iPosition - 1)



        ' Parse string for further processing

        sDuration = Right(sDuration, Len(sDuration) - iPosition)



        ' Get middle time component

        iPosition = InStr(sDuration, ":")

        If iPosition = 0 Then

           ' No more time info, must just be mm:ss format

           vMinutes = vHours

           vSeconds = sDuration

           vHours = 0

        Else ' Time info must be in hh:mm:ss format

           vMinutes = Left(sDuration, iPosition - 1)

           vSeconds = Right(sDuration, len(sDuration) - iPosition)

        End If



     End If



     ' Represent all components in terms of seconds

     vHours = vHours * 3600

     vMinutes = vMinutes * 60



     ' Return total seconds value

     ConvertStringtoTotalSeconds = CInt(vHours) + _

        CInt(vMinutes) + CInt(vSeconds)



  End Function 'ConvertStringtoTotalSeconds


As you can see even from this relatively straightforward example, isolating a bug by visually inspecting the code is an inexact process as well as a slow, tedious way to solve problems. Fortunately, there are better, easier, more precise ways to hunt down the error.

Using the MsgBox Statement

The debugging difficulty presented by Pace-Pal is that you can't tell where things start to go wrong. As a matter of fact, "start" to go wrong is rather misleading. Things really go wrong all at once, with no gradual transition, because VBScript treats any runtime error as fatal! So the first step is to hone in on your error. Many languages come with development environments that help you easily monitor the flow of your program and pinpoint such errors. Unfortunately, VBScript does not.

If you've debugged in other environments, however, one obvious tool to pinpoint the rogue statement might come to mind: the MsgBox statement.

NOTE
The MsgBox statement displays a message box on top of the Web page that the user must respond to before the program continues. In debugging, MsgBox is useful because it interrupts the normal flow of the program, and that interruption easily can be seen by the user. What's more, the program can provide useful debugging information to the programmer, as you will see later in this section.

When this function is encountered, the designated message is displayed to the user and the flow of your program halts until the user clicks the message box button to acknowledge the message and proceed.

That means that the MsgBox statement gives you a way to tell where your program's execution is. Therefore, it gives you a way to get insight into the exact location of a runtime error. Suppose that you're chasing an error in your program. You insert the following statement in the middle of your program and rerun it:


MsgBox "I made it this far without my program choking!"

If your program displays this message when you rerun it, you have some additional insight into the error you're chasing. You know the runtime error was not caused by any statement preceding the MsgBox call. The error lurks in some statement after that point in your code. For the next step, you can shift the MsgBox statement down a line and rerun the test. If that works, do it again. And again. And again-until you hit the line that causes the runtime error.

Alternatively, you can take the tried-and-true "narrow it down one step at a time" approach. You put your MsgBox statement halfway through your code and see whether it is reached. If so, you know the problem must be in the last half of your code statements. Put it halfway through the remaining statements. If that test is successful, put it halfway through the new, smaller remaining section of statements. And again. And again-until you hit the runtime error. If the flow of code is not sequential, the process of isolating the problem is even tougher. Suppose that a line of code calls another routine that branches to one path of a Select Case statement, which in turn calls another routine as the result of an If condition. In such a case, determining where to put the message box traces in advance becomes very difficult, not to mention complex!

If both these approaches sound similarly tedious and time-consuming, that's because they are! You can save yourself a few test runs by starting right out with a MsgBox statement after each and every statement, each with a different message. Suppose that your program consists of these statements:


Sub Test_OnClick



   Dim a, b, c



   a = text1.text * 3

   c = text2.text * 4

   d = a + c



End Sub

You then can modify it to get a MsgBox-based program flow trail:


Sub Test_OnClick



   Dim a, b, c



   MsgBox "Point 1"           ' We've made it to point 1

   a = text1.text * 3

   MsgBox "Point 2"           ' We've made it to point 2

   c = text2.text * 4

   MsgBox "Point 3"           ' We've made it to point 3

   d = a + c

   MsgBox "Point 4"           ' We've made it to point 4

End sub

If your program runs and then dies with a runtime error, the MsgBox statement that last appeared on-screen tells you right where the problem is. This method of tracking down the rogue statement does work, but it takes time to insert all the statements and then remove them after you finish debugging. There is nothing wrong with this approach if your program is small. If your program is large, however, there are better, sleeker, quicker ways to chase down the bug. You just have to reach deep into the VBScript bag of tricks and pull out another language construct: the On Error Resume Next statement.

Using the OnError ResumeNext Statement

It would be nice if a program could simply continue after it caused a runtime error. That way, at some later point in the script, you could use code statements to learn whether the end of a procedure was reached successfully to print the values of variables for you to inspect, or to show you the result of calculations. If a program had the perseverance to forge on after it hit rough waters so that you could retrieve this information, the debugging task would be easier.

There's another reason, too, that you might wish your program could survive a runtime error. Although VBScript is trying to save you from bad data or results when it produces a runtime error, there are cases in which you might prefer to continue execution after the error occurs, even if it means living with bad results. Your program might calculate extra information such as the number of calories a runner burns or how much perspiration the runner generates, for example. If so, it could be quite annoying to your user to halt the whole program just because he entered one piece of extraneous information incorrectly.

If you want to perform additional debugging after the rogue statement, or if you don't want the program to crash if an error occurs, you need a way to override the abort. Fortunately, VBScript gives you this override power. It comes in the form of the On Error Resume Next statement. This statement tells the VBScript Interpreter that, when an error is encountered, it should simply ignore it and continue with the next statement. Listing 12.2 shows an example of this statement applied to the Pace-Pal problem.


Listing 12.2. Code made more robust with an On Error statement.

  Function ConvertStringToTotalSeconds (ByVal sDuration)



     '--------------------------------------------------------------------------

     ' Purpose: Takes HH:MM:SS format string and converts to total seconds

     '--------------------------------------------------------------------------



     ' When error occurs, continue with next statement

     ' rather than halting program

     On Error Resume Next



     Dim iPosition       'Position of ":" seperator

     Dim vHours          ' Number of hours required

     Dim vMinutes        ' Number of minutes required

     Dim vSeconds        ' Number of seconds required


NOTE
The modified Pace-Pal program with the change shown here is located on the accompanying CD-ROM in the file Ppalerr1.htm.

When the On Error Resume Next statement is used, you don't get any frightening messages of gloom and doom, and your program doesn't come to a screeching halt. Instead, the VBScript Interpreter continues with the next statement, leaving your user none the wiser. Figure 12.4 shows the results of running Pace-Pal with this modification. Note that the program manages to provide a final pace result, albeit an incorrect one, in the pace box. But it will work correctly on any valid input that is subsequently entered, even without reloading the page.

Figure 12.4 : The Pace-Pal program faced the bug and lived to tell about it!

Alas, all is not perfect; there is a problem with the On Error Resume Next approach. The On Error Resume Next statement gives you a way to ignore errors when they occur, as the Pace-Pal example demonstrates. Unfortunately, when you use this statement, you run the risk that you won't find out about a problem in your program that you really do care about. For that matter, unless you know specifically what caused an error, it rarely is safe just to ignore it.

If you've used other languages with error handling, particularly VBA or Visual Basic 4.0, you might realize that most error-handling systems provide additional flow-control capabilities when errors occur. In Visual Basic 4.0 or VBA, for example, you can use the On Error Goto statement to direct your program flow to a specific area of error-handling code. For example, On Error Goto Shared_Error_Handling directs the program flow to one specific block of error-handling code labeled Shared_Error_Handling whenever an error occurs. This capability is not available in VBScript. The only thing you can do with the On Error statement is to tell it to Resume next-that is, to continue on to the next statement. If an error occurs in a procedure while this statement is in effect, your program simply moves on to the next statement in the procedure. Once again, though, the VBScript language comes to your rescue. There is a way to use On Error Resume Next wisely. You must couple it with the power of the Err object.

Using the Err Object

If you combine On Error Resume Next with a special VBScript object called the Err object, your program not only can survive runtime errors, but it can even incorporate program logic that analyzes these errors after the fact. The Err object is useful for two reasons: It can be a great help in debugging and, in some cases, it can be a feature you want to incorporate into your final program to make it more robust. When an error occurs in VBScript, the Err object analyzes the error to see what kind of a problem, if any, occurred within a body of code. You can display the error code and description after an error occurs and still let your program continue on for further debugging after you display this information. You even might choose to directly build recovery techniques into your programs. If the Err object tells you that data of the wrong type was used in a calculation, for example, you can prompt the user to reenter the data.

NOTE
For an in-depth treatment of the Err object, refer to Chapter 10, "Error Handling." In this chapter, the Err object is considered specifically with respect to debugging your VBScript applications.

The Err object is an intrinsic VBScript object. That means that you don't have to do any work to use it. No special declarations are required. You can simply reference it anywhere in your code and inspect the current error-related property values of this object. These properties provide several important pieces of information:

There are two methods for using the Err object, which are explained more fully in the next section:

Using the information from the Err object, you can check whether an error occurred and then examine relevant information to aid you in debugging a detected error. It's easy to check whether an error has occurred. If Err.Number equals 0, no problems were detected. Any other value represents an error code. If you do find an error code, the Err.Description field provides the standard text description of the error. This message is the same one that pops up on the runtime message error box when the program screeches to a halt if you aren't using On Error Resume Next to ignore the errors. You can even look at the originator of the error in the Source property. Most often, the source is the name of your VBScript file itself if it was your VBScript code that caused the problem. In some cases, however, you might find that the error source is a component you have integrated, such as an ActiveX control. You can even get information on associated Help files for errors, although this information is less likely to be of use to you in structuring your error-recovery code. The code sequence in Listing 12.3 shows one example of how you might check to see whether an error has occurred.


Listing 12.3. Checking the Err object to see whether an error has occurred.

Sub Test_OnClick



   On Error Resume Next



   Dim a



   a = text1.text / text2.text



   If Err.Number <> 0 Then

      Msgbox "Error : " & Err.Description & " from " & Err.Source

   End If

End Sub


Code analysis of the Err object like that shown in Listing 12.3 also can be applied to debugging problems like the Pace-Pal situation. If On Error Resume Next is used to turn off runtime error reporting and aborting, you can look at the end of the suspect procedure to see whether any errors occurred within it. If errors did occur, you can use the Err object to print full details. One important consideration with this approach, however, is that if more than one error occurred within the procedure, you see information only on the most recent error. Still, this is helpful when you simply want to know whether a block of code is error free. If an error occurred, you can add additional error checks and debug further to determine whether there were multiple errors. Listing 12.4 shows an example of the Pace-Pal code with a check inserted at the end of a procedure.


Listing 12.4. Code with additional error diagnostics from the Err object.

           vMinutes = Left(sDuration, iPosition - 1)

           vSeconds = Right(sDuration, Len(sDuration) - iPosition)

        End If

     End If



     ' Represent all components in terms of seconds

     vHours = vHours * 3600

     vMinutes = vMinutes * 60



     ' Return total seconds value

     ConvertStringtoTotalSeconds = CInt(vHours) + CInt(vMinutes) + CInt(vSeconds)



     If Err.Number <> 0 Then

        Msgbox "Error #:" & Err.Number & " Description:" & Err.Description _

          & " Source:" & Err.Source, 0, "Error in ConvertStringtoTotalSeconds!"

     End If



  End Function ' ConvertStringtoTotalSeconds


NOTE
The source file for Pace-Pal with the change shown here is located on the accompanying CD-ROM in the file Ppalerr2.htm.

Notice that this code prints information only if the error occurred. If the error did not occur, the user is not disturbed by error information. Figure 12.5 shows the results of this error check. As the figure shows, the error check added to the Pace-Pal code does detect an error within the procedure. The cause is identified clearly as a type-conversion error, and the source is pegged to be the script itself.

Figure 12.5 : Information about the error.

Bolstered by the assistance of the Err object, the Pace-Pal code is more robust and provides you with more insight. It is more robust because, despite the error, the program still continues through its code path and produces results without aborting. In this case, a pace of 00:00 is presented as the result because there is no valid data to work with. You have more insight because you can be certain that an error occurred in the ConvertStringtoTotalSeconds function. Additionally, you know precisely which error occurred within that function and that error's source. Armed with this information, you can isolate the cause of the error using the following tracing techniques. But before tackling the isolation steps, it helps to fully understand the intricacies of these error-handling mechanisms. Next, you'll learn a few more advanced details of the Err object and the On Error Resume Next statement, and then look at more tracing techniques. You'll then return to the quest for your Pace-Pal bug.

Taking the Err Object Further

The On Error Resume Next statement, also called an error handler, is a procedure-level statement. It remains in effect only within the procedure that contains the On Error declaration. Imagine that a higher-level procedure that uses On Error Resume Next calls a lower-level procedure that does not use it. If an error occurs in the lower level procedure, the flow of statements in that procedure halts immediately. VBScript prepares to alert the user of the error, but before doing so, it checks whether any higher level procedures with an On Error Resume Next function were in the process of calling this lower level procedure. If they were not, the error is treated as a normal runtime error, halting the program and displaying the runtime error to the user. However, if a higher-level procedure with an On Error Resume Next function was calling a lower-level procedure with no error handling, when the lower-level procedure causes the error, it is addressed by the higher-level procedure.

This commonly is termed raising the error. The VBScript Interpreter, like many languages, raises an error to higher calling levels until it finds a procedure with error handling. If none is found, the script halts and the user is presented with the error results by the interpreter.

After the error is passed to that higher-level procedure, that procedure's error-handling Resume Next rule goes into effect. That procedure provides instructions to continue to the next statement when an error occurs, so execution picks up right after the call to the lower-level procedure that went awry.

You can't expect to analyze Err object information within a procedure unless that procedure contains the On Error Resume Next statement. As long as this statement exists within the procedure, errors do not cause the procedure to be halted. If the VBScript Interpreter finds that no higher-level procedure with an error handler that was calling the procedure caused the problem, the entire program halts. The bottom line is that if you want to use the Err object to carry out any error analysis in a procedure, make sure that an On Error Resume Next first appears within that procedure.

The Err object itself has a couple more interesting capabilities: the Clear and Raise methods. The Raise method generates an error. More precisely, it simulates an error. In other words, you can tell Raise what kind of error you want to simulate. To simulate a certain type of conversion error, for example, you can use the statement


Err.Raise 13

to have VBScript respond in its normal fashion, just as if it had encountered an actual code statement that caused an error of error code type 13. Raising an error causes the program to behave exactly as if it had encountered a real error. If any procedure in the active lineup of the current and calling procedures has an On Error Resume Next statement, the program flows to the next applicable statement. In such a case, the Err object then contains the appropriate information for the error raised. For example, Err.Number equals 13. On the other hand, if there is no On Error Resume Next in the active lineup of calling and current procedures, the program treats the raised error as a regular runtime error, displaying a message to the user and terminating the program.

You might be thinking that it would take a pretty twisted programmer to purposely inject a simulated error into his code. Such a tactic is warranted, however, in some situations. (Naturally, the VBScript development team at Microsoft wouldn't have included this method if it could be used only for evil purposes!) One way you might use this method for good is to evaluate the Err object within a procedure to determine the severity of potential problems. You might write code that inspects Err.Number to determine whether the problem is a minor one that won't affect results or a major one that presents critical problems to the program. In the event of a minor problem, you might decide to write code that continues with the normal flow of statements in the current procedure.

For a major problem, however, it might be imprudent to continue with the program after the detection of an error. In that case, you might want the calling procedures at higher levels to address the error without going any further in the current routine. There is an easy way to redirect the program flow back to the error-handling code in the higher-level procedures. If those calling procedures have On Error Resume Next defined, you can simply raise the error with Err.Raise, and control flows to the first higher level calling procedure that has an active error handler.

To have full mastery of VBScript error handling, one more technique remains: the Clear method. A little insight into the Err object and the way it gets cleared is necessary to understand what Clear does. The Err object, as you have learned, keeps a record of information on the last error that occurred. When an error occurs, the appropriate information is loaded into the Err object by the VBScript Interpreter. If this information lingers forever, though, it can cause some headaches. If an error were set in Err.Number indefinitely, you would end up addressing the same error over and over.

For this reason, VBScript clears the Err object whenever your flow of statements reaches the end of a subroutine or function that contains an On Error Resume Next, or whenever an On Error Resume Next statement itself is encountered. All fields are set to their initial state. Any Err.Number containing an error code resets to 0 (indicating no error). Using On Error Resume Next at the start of each procedure guarantees that old errors from previous procedures no longer will be stored in the Err object. You get a clean slate.

This works fine if you just check the value of the Err object once within each procedure. But what if you have multiple places within the same procedure where you check Err.Number? What if your code checks the value after each and every statement? Then, if the first statement causes an error, Err.Number is set accordingly. If you check Err.Number immediately after that statement, you will correctly detect the error, such as a type-conversion error. But all the subsequent statements within the same procedure that check Err.Number still will find the old type-conversion error indicator for the error that already was analyzed, even if the most recent statement caused no error.

If you use Err.Number many times within a procedure, you should ensure that you're not reacting to leftover error data. Fortunately, VBScript provides a means to do this: the Err.Clear method. This method resets all fields of the Err object and assigns Err.Number back to 0 to indicate no error. So when you want to make sure that you are starting with a clean error slate, simply insert an Err.Clear into your code. Typically, this is carried out right after an error is detected and addressed. Some caution must be exercised with this method, however. There is nothing to prevent you from clearing errors that have not yet been addressed. Make sure that you use Err.clear only after checking the Err.number and carrying out any handling needed.

There are many error-handling strategies that can be built on the On Error Resume Next statement and the Err object. If you don't use On Error Resume Next at all, your runtime errors will show through to you (or your user) loud and clear! If you do use the On Error Resume Next statement, you risk inadvertently ignoring errors, unless you diligently check the status of the Err object in every routine that uses On Error Resume Next. When you check error codes, you must remember that error codes still may be set from previous statements unless some action has occurred to clear them.

When writing your error-handling code for higher level procedures that use On Error Resume Next, you must remember that errors can trickle up. This is true whether those errors are natural errors or "simulated" errors caused by Err.Raise. Errors that occur in lower-level procedures can trickle up into your higher-level procedure if lower levels do not use On Error Resume Next. That means that you might need to insert statements that give you more details about an error to pinpoint the cause of it. Fortunately, additional, hand-crafted techniques are available to trace and understand your code. So next you'll learn about what tracing code is really all about.

Using Advanced Debugging Techniques

Thus far, you have learned how to use the message box statement, the Err object, and On Error Resume Next statements to handle errors. To better isolate errors in your code, you need more sophisticated ways to trace your code.

Tracing your code is the act of following the flow of statements as your program progresses. Usually, code is traced to isolate bugs and solve problems. You also might trace code simply to better understand the inner workings of a block of code. You already saw a rudimentary form of tracing earlier in the lesson-simply insert MsgBox function calls into the code, run your program, stand back, and watch where message boxes pop up. Because you know where you inserted the MsgBox calls, you easily can follow the progression of the code.

This approach works reliably and is easy to implement, but it does have some drawbacks. It takes some effort to insert the statements. Then, when you run the program, you must interact with every message box you insert, even if no error occurs. If you've inserted 150 message boxes to trace the flow of your program, it can be rather tedious to respond to each and every one!

There are more powerful, elegant ways to trace code, however. Inserting and responding to a series of message boxes can be a cumbersome task. In addition, an important part of code tracing can consist of watching the values of variables while tracking the flow of statements. Data values and knowledge of the last statement processed often must be viewed in tandem to understand the state of your program and its behavior. There are ways to achieve this type of tracing in VBScript, which is the subject covered in this section.

Tracing Your Code Using the Message Box

There is an easy way to avoid responding to each and every message box in the course of tracing a program. This alternative method consists of combining two aspects of the VBScript language. You've already looked at both halves of the equation; now you just need to join them. If you use On Error Resume Next in your program, you have seen that not only will your program survive any errors, but you also will have ready access to error information through the Err object. This object tells you whether an error occurred. The message box gives you an easy way to display that status.

If you can be assured that you will see a message after an error occurs, there is no need to view the status of the program if no problems have been detected. You can make the message box trace more elegant by displaying only trace information if an error actually occurred. You achieve this by placing a pair of message box statements around the line of code you suspect contains errors. When the trace feedback is displayed, full details on the type of error can be provided. This technique is shown in the modified Pace-Pal code in Listing 12.5.


Listing 12.5. Tracing the flow with a message box statement.

' . . . SAME CODE UP TO THIS POINT AS SHOWN IN PREVIOUS LISTINGS



           vMinutes = vHours

           vSeconds = sDuration

           vHours = 0

        Else ' Time info must be in hh:mm:ss format



           vMinutes = Left(sDuration, iPosition - 1)

           vSeconds = Right(sDuration, Len(sDuration) - iPosition)



        End If

     End If



     ' Represent all components in terms of seconds

     vHours = vHours * 3600

     vMinutes = vMinutes * 60



     If Err.Number <> 0 Then Msgbox "An error is present prior"



     ' Return total seconds value

     ConvertStringtoTotalSeconds = CInt(vHours) + _

        CInt(vMinutes) + CInt(vSeconds)

     If Err.Number <> 0 Then Msgbox "An error is present here"


An informative tracing message is generated when this script is run, as shown in Figure 12.6.

Figure 12.6 : Output from the message box trace.

Now to trace your program, you no longer have to click on trace message boxes when everything is going okay. If you see a message box come up, you know it's coming from an area of your code that detected an error.

NOTE
The modified Pace-Pal program with the change shown here is located on the accompanying CD-ROM in the file Ppalerr3.htm.

Saturating Your Code with the Message Box

Using a single pair of message box trace statements might be sufficient if you have a pretty good idea where your problem is. But if you have a really tough problem and want to make sure to cover all the bases, it might be just as easy to insert a trace after each and every statement. That way, you virtually guarantee that you will pinpoint the exact statement that causes the problem.

When you take this approach, remember to clearly and uniquely identify each trace statement through the message box text. It does little good to add 200 trace statements to a program if they all just say Error has been detected!. If you run the program and only Error has been detected! pops up on your screen, you have no idea which statement the message originated from!

The more descriptive the trace messages, the better. If you have messages spread across more than one procedure, it is helpful to identify the procedure name in the message. The idea is that when you see the message, it quickly leads you to the corresponding location in the program. Listing 12.6 shows the Pace-Pal program with extensive trace messages added. Each is uniquely identified within the message text.

NOTE
The modified Pace-Pal program with the change shown here is located on the accompanying CD-ROM in the file Ppalerr4.htm.


Listing 12.6. Tracing the flow with many message box statements.

' . . . SAME CODE UP TO THIS POINT AS SHOWN IN PREVIOUS LISTINGS



           vMinutes = vHours

           If Err.Number <> 0 Then Msgbox "Error occurred prior to Point A!"

           vSeconds = sDuration

           If Err.Number <> 0 Then Msgbox_"Error occurred prior to Point B!"

           vHours = 0

           If Err.Number <> 0 Then Msgbox "Error occurred prior to Point C!"



        Else ' Time info must be in hh:mm:ss format



           vMinutes = Left(sDuration, iPosition - 1)

           If Err.Number <> 0 Then Msgbox "Error occurred prior to Point D!"

           vSeconds = Right(sDuration, Len(sDuration) - iPosition)

           If Err.Number <> 0 Then Msgbox "Error occurred prior to Point E!"



        End If



     End If



     ' Represent all components in terms of seconds

     vHours = vHours * 3600

     If Err.Number <> 0 Then Msgbox "Error occurred prior to Point F!"

     vMinutes = vMinutes * 60

     If Err.Number <> 0 Then Msgbox "Error occurred prior to Point G!"



     ' Return total seconds value

     ConvertStringtoTotalSeconds = CInt(vHours) + CInt(vMinutes) + CInt(vSeconds)

     If Err.Number <> 0 Then Msgbox "Error occurred prior to Point H!"



     If Err.Number <> 0 Then

        Msgbox "Error #:" & Err.Number & " Description:" & Err.Description _

          & " Source:" & Err.Source, 0, "Error in ConvertStringtoTotalSeconds!"

     End If



  End Function ' ConvertStringtoTotalSeconds


The message resulting from this modified code runs with the same program input presented earlier is shown in Figure 12.7.

Figure 12.7 : Output from tracing with sequential message boxes.

From reading this trace message and looking at Listing 12.6, it should be clear exactly which statement caused the problem: the ConvertStringtoTotalSeconds = statement. If you had inserted just one trace statement at a time, it might have taken many debug iterations to come to this conclusion. Although you spend more time editing the code when you insert multiple trace statements, you can hone in on the specific problem statement after many fewer iterations.

Watching Your Code Using Variables and the Message Box

So you've located the statement that is causing the problem, but you don't know why the error is occurring or how to fix it. That often takes further debugging; it typically requires a look at the contents of the variables if those variables are involved in the rogue statement. One way to get this information is to print the contents of the variables and the variable subtypes right before the problem statement. This technique is applied to the Pace-Pal program in List-ing 12.7.


Listing 12.7. Tracing variable contents with a message box statement.

     ' Return total seconds value



     MsgBox "vHours = " & vHours & " with type = " _

& vartype(vHours) & _

         "    vMinutes = " & vMinutes & " with type = " _

         & vartype(vMinutes) & _

         "    vSeconds = " & vSeconds & " with type = " _

         & vartype(vSeconds),0, "Var Dump"



    ConvertStringtoTotalSeconds = CInt(vHours) + _

       CInt(vMinutes) + CInt(vSeconds)



    If Err <> 0 Then

        Msgbox "Error #:" & Err.Number & " Description:" & Err.Description _

          & " Source:" & Err.Source, 0, "Error in ConvertStringtoTotalSeconds!"

    End If



End Function  ' ConvertStringtoTotalSeconds


Figure 12.8 shows the results of this variable trace.

Figure 12.8 : Output from the message box variable trace.

A full description of the variables is now available. One variable is empty, another has an integer value, and another contains string data. Even after viewing this information, you might be confused over exactly what causes the error. You can take yet one more step to shed light on the problem.

Breaking Apart Complex Statements to Find Bugs

The problem now has been isolated to one statement, and the values and subtypes of the variables prior to that statement are known. In the problem statement, the CInt (convert to an integer) function is applied to both a variable that is empty and to a variable that contains a string. How can you see which of these is the problem conversion, or whether both are to blame? The problem statement consists of multiple pieces or expressions, any of which might be the cause of the problem. Your next goal should be to isolate the problem to just one of these pieces. Therefore, the next step is to break it down into smaller pieces, and then apply the same trace techniques to those subpieces. Listing 12.8 shows the modified Pace-Pal script with the problem statement broken down into smaller pieces that can be traced individually.


Listing 12.8. The complex statement broken down into multiple simpler statements.

Loop



    ' Temporary code to isolate problem to one statement



    If Err.Number <> 0 Then Msgbox "Error prior to debugA: "

          & Err.Description

    debugA = CInt(vHours)

    If Err.Number <> 0 Then Msgbox "Error after debugA: " _

          & Err.Description

    debugB = CInt(vMinutes)

    If Err.Number <> 0 Then Msgbox "Error after debugB: " _

          & Err.Description

    debugC = CInt(vSeconds)

    If Err.Number <> 0 Then Msgbox "Error after debugC: " & _

         Err.Description

    ConvertStringtoTotalSeconds = debugA + debugB + debugC



    ' Return total seconds value

    '  ***Decomposed above for Debug ConvertStringtoTotalSeconds = _

    ' CInt(vHours) + CInt(vMinutes) + CInt(vSeconds)



    If Err.Number <> 0 Then

        Msgbox "Error #:" & Err.Number & " Description:" & Err.Description &  _

            "Source:" & Err.Source, 0, "Error in ConvertStringtoTotalSeconds!"

    End If



End Function  ' ConvertStringtoTotalSeconds


When the script is run after this change, the message box specifically highlights which piece causes the error. Figure 12.9 shows the error message generated when the CInt function, when applied to a string that contains non-numeric characters, causes VBScript to generate an error.

Figure 12.9 : Output from the first message box trace after the error, with a trace on decomposed statements.

This is the cause of the original runtime calamity first encountered at the start of the chapter.

Using Other Tools to Help You with the Debugging Task

The example in Listing 12.8 shows the use of many trace statements in just one area of code. In some cases, you might find that inserting one or more trace statements and moving them around is an effective strategy. If you have a 100-line script but suspect that the problem is somewhere in the first five lines, for example, you might put the error check after line 5. If the trace indicates an error at this location, it simply means that the error occurred somewhere on or prior to that line. To prove exactly where within the first five lines the error occurs, your next step might be to move the trace statement so that it comes right after line 4, and so on.

This type of trace statement movement actually is quite typical of debugging efforts. A statement is moved in the HTML page editor, the browser is activated, and the page is reloaded to test the effect of the change. The same cycle is repeated as often as necessary. As a result, you might find that much of your debugging time is spent in transition between your page editor and browser. It is worth noting that the mechanics of making such changes and testing them can consume a significant part of your debugging time. Take careful stock of the tools you have available, and find a process that works well for you.

One approach that works well in the Windows environment is to simply have the Notepad text editor loaded with your source file. You can specify View-Source from the Internet Explorer 3.0 menu to launch Notepad. Then you can use Notepad to modify your script and save it. After you save the script, however, don't close Notepad. It doesn't hurt to leave it up and running. Simply activate the browser, and then reload your page to pick up the new modifications. You can do this in Internet Explorer by pressing the f5 function key. After your modified page is loaded, you can test your script. When you find that more changes are needed, just shift back to the still-open Notepad and repeat the cycle. You avoid the time hit of reopening the editor by using this method. You can take a similar approach with the ActiveX Control Pad editor from Microsoft.

This Notepad scenario is outlined here not to emphasize how to best work with Notepad, but to stress that, whatever your tool, you need to put some thought into how you are applying it. The idiosyncrasies of interacting with it will be multiplied many times over, because debugging, and particularly tracing, often is a tedious, repetitive process. As more tools become available, choosing the right tool is likely to become more and more critical.

Because technology and tool sets are evolving almost on a daily basis, simply finding out about the right tools for debugging can be a challenge. The best way to get an up-to-date view of available tools for VBScript is to search the Web for current information. Resources such as www.doubleblaze.com present you with a summary of many of the important and useful tools. Microsoft's Web site, www.microsoft.com, provides additional sources of information.

Using VBScript Versus Traditional Debugging Environments

When you get a browser that supports VBScript, you do not get a VBScript development environment along with it. You are left to your own devices to decide how to construct the segments of VBScript code you insert in your Web pages. You might build VBScript programs in an HTML-generation tool, Microsoft's ActiveX Control Pad editor, or you could generate them directly in a text editor. No matter how you're doing it, odds are it's not in a dedicated VBScript development environment with the full-fledged debugging features of Visual Basic 4.0. Although such tools may materialize over time, they do not exist currently. By contrast, almost all high-end computer languages do have their own sophisticated development environment. Typically, a special editor is used for producing programs; the editor sometimes can help check syntax as you type the programs. Also, these environments often include powerful debugging environments that assist with the task of tracing a program and isolating problems. The Visual Basic 4.0 environment and VBA 5.0 offer a rich set of debugging facilities. Although you can't use these debugging facilities directly with VBScript, you can apply some of the same concepts with the "build-it-yourself" trace techniques you'll learn about later in this chapter.

Using Visual Basic to Debug VBScript Applications

Visual Basic 4.0 has a wealth of debugging tools and capabilities that VBScript does not have. If you are a Visual Basic programmer, however, you can take advantage of Visual Basic to debug your VBScript programs. How is this done? This section gives you the answers.

Using Visual Basic 4.0 Trace Capabilities

The Visual Basic 4.0 programmer has no excuse for not having keen insight into all areas of his source code. Visual Basic 4.0, the high-end member of the Visual Basic family, has powerful, easily controlled debugging facilities. Visual Basic 4.0, for example, enables you to stop the program at any location simply by selecting a line of source code in the program editor and pressing a function key to designate the line as a temporary breakpoint. Upon hitting this breakpoint, the running program screeches to a halt, enabling you to spring into debugging action. You can inspect the value of variables in a special debugging window provided by the Visual Basic 4.0 design environment. You can even type more complex expressions in the window. This enables you to evaluate any other statements that might help you understand the problem. You can use this window to inspect the current state of variables even as your program remains suspended. Figure 12.10 shows a view of the Visual Basic 4.0 development environment with a suspended program that has reached a breakpoint.

Figure 12.10 : The Visual Basic 4.0 debugging environment.

After you check out the state of your suspended program, you can send it on its merry way, right where it left off. You can have it proceed one statement at a time, providing you with the opportunity to query its state line by line. You also can just let it continue until program completion or some other predefined breakpoint. You can even tell it to pick up at an entirely different program location than the one where it stopped. And if you're even nosier about what is going on, you can make arrangements to automatically monitor the contents of your favorite variables as the program chugs along. As variable values change, the new values are displayed automatically in the debug window without stopping your program. You can even provide instructions for your program to stop and show you the last statement it carried out if a variable reaches a certain value.

By now, your head probably is spinning from this dizzying array of debugging weapons, and you're wondering how many of them apply to VBScript. Unfortunately, the answer is that virtually none of these whiz-bang Visual Basic 4.0 debugging features is available to you as you develop VBScript programs. No inherent means exists through the browser or standard text editor to place breakpoints on VBScript programs, to pop up a debug window, to make your program leap to a new location from a suspended state, or to automatically monitor the contents of variables.

On the other hand, several rays of encouragement offset this lack of tools. One comforting thought for the long term is that, as the language matures, tools will likely come. True, that doesn't help you for the short term, but even today's VBScript provides the foundation you need to build similar trace and monitoring capabilities right into your programs. It takes a little extra effort, but as you'll see in the sections that follow, the techniques are really quite easy to apply.

NOTE
You can find further information on some VBScript development tools as they become available at www.DoubleBlaze.com. Many utilities also are available for various aspects of Web page authoring at the Microsoft site at www.microsoft.com/intdev.

Debugging VBScript Code Within Visual Basic

If you happen to be one of the million plus Visual Basic 4.0 or Visual Basic for Applications programmers, you have a secret weapon you can use in developing VBScript code. Try writing the code in Visual Basic first, and then move it to VBScript! You can take full advantage of the rich debugging capabilities of Visual Basic as you get the bugs out of your program. Then, when it is stable, you can move it to VBScript.

A note of caution, however: There's no such thing as a free lunch. Likewise, there's also no such thing as a free debugger (or at least so it seems). Language differences exist between Visual Basic and VBScript. A program that works fine in Visual Basic 4.0 can be obstinate in VBScript. VBScript is a subset of VBA, so much of what works in your Visual Basic application will not work in your script. Depending on your knowledge of the two languages, it can take some work to weed out the syntax differences as you move the code over from Visual Basic 4.0 to VBScript. Some of the language differences are subtle and might not be immediately obvious, even if you know both languages fairly well. The bottom line is that if you debug your code in Visual Basic first and get it running in Visual Basic, don't assume that you're home free. Porting work still might lie ahead in moving the code to VBScript. Nevertheless, Visual Basic's debugging tools make the "debug, then port" process more effective for larger or more complex scripts.

Handling HTML Errors

So far, you have been looking at techniques to debug VBScript applications. You should be aware of the fact, however, that your programs typically are contained within HTML documents. In this case, VBScript works hand-in-hand with HTML, and you must write both VBScript and HTML code to present a Web page to the user. One thing HTML does have going for it is a rich set of tools that can aid in quickly developing well-structured Web pages. If you don't have one of those tools, however, or if you have a low-end tool, debugging HTML itself can be the cause of serious hair pulling and grimacing.

It is just as easy to create errors in VBScript as it is in HTML. So how does HTML resolve errors? Consider the HTML shown in Listing 12.9, and note the <A that marks the beginning of an anchor reference.


Listing 12.9. Normal HTML.

<H1><A HREF="http://www.mcp.com"><IMG ALIGN=BOTTOM

SRC="../shared/jpg/samsnet.jpg" BORDER=2></A>

<EM>Pace-Pal Sample 3</EM></H1>


Suppose that this markup language had been mistakenly entered with just one character different. Assume that the < was inadvertently omitted from in front of the A, as shown in Listing 12.10.


Listing 12.10. HTML missing a tag.

<H1>A HREF="http://www.mcp.com"><IMG ALIGN=BOTTOM

SRC="../shared/jpg/samsnet.jpg" BORDER=2></A>

<EM>Pace-Pal Sample 3</EM></H1>


The effects of such an omission are ugly indeed, as you can see in Figure 12.11.

Figure 12.11 : Pace-Pal with a missing tag.

Instead of creating the anchor, the internal details of creating the anchor are displayed on-screen. To make matters worse, not only are the results ugly, but they also result in a Web page that doesn't work as intended. The page now has a nonfunctioning link. HTML has no runtime sequence of logic to step through to help pinpoint the error. Instead, it is just a markup or page-generation instruction set. In the absence of sophisticated authoring tools for HTML, the only way you can debug the code is the old method of inspecting it visually. You must look at the page, visually scan it, and review each tag, one by one, for proper syntax. As a general rule, you first should ensure that your HTML code is clean by making a visual inspection before proceeding to debug the VBScript code itself.

Making Sure the Bugs Are Dead

Once you know the cause of the bug, fixing it is often easy. First, decide what type of fix you want to put in place. In most programs, like Pace-Pal, many solutions are available. Pace-Pal can immediately check that data entered by the user is in the correct format and demand that the user reenter the data if it is invalid, for example. Alternatively, Pace-Pal can check the data and drop illegal extra characters without telling the user. Then again, Pace-Pal simply can convert any invalid data to 0 and let the user know. The possibilities are many.

For the sake of simplicity, the last solution mentioned is used in this example. Although this solution isn't necessarily the best way to handle the fix when viewed in the context of the whole program, it does eliminate the error. A check for invalid data is made right before the problem statement. If invalid data is found, the user is informed and all times are forced to be 0. Because this is a legal numeric representation, the type-conversion problem is avoided and no error occurs. Listing 12.11 shows this solution.


Listing 12.11. The fix that our trace pointed us to!

    ' If there is invalid string data, warn the user and

    ' reset data to prevent more errors

    If (Not IsNumeric(vHours)) Or (Not IsNumeric(vMinutes)) _

        Or (Not IsNumeric(vSeconds)) Then

          Msgbox "Time contains character when digits expected. " & _

                 " Please respecify!", 0,"Invalid time"

          vHours = 0

          vMinutes = 0

          vSeconds = 0

     End If



     ' Return total seconds value

     ConvertStringtoTotalSeconds = CInt(vHours) + CInt(vMinutes) + CInt(vSeconds)



     If Err.Number <> 0 Then

         Msgbox "Error #:" & Err.Number & " Description:" & _

             Err.Description & " Source:" & Err.Source,

             0, "Error in ConvertStringtoTotalSeconds!"

     End If



End Function  ' ConvertStringtoTotalSeconds


With this fix, the program will be robust enough to continue even if a user enters invalid input. Figure 12.12 shows the resulting message box. The program informs the user of the error, substitutes a time of 0 in place of the input, and calculates a pace of 0 seconds per mile.

Figure 12.12 : User feedback from the bug-proofed Pace-Pal.

The next step after inserting the fix is to verify that it worked. In this case, that verification is relatively easy. This fix addresses a type-conversion error that prevented Pace-Pal from calculating a final pace. Because the code that checks the error status is still in place at the end of the procedure, simply rerun the procedure. If you don't get an error message, you know the type-conversion error has been eliminated. Likewise, the fact that a pace of 00:00 shows up in the pace text box indicates a complete calculation. So, in this case, verifying that the fix really solves the problem is relatively easy. For some bugs, you might have to add more trace statements or variable analyses after the fix is in place to verify that it had the intended results.

It is important to note that this fix keeps the program from crashing when the user enters an invalid time such as 37:12AA. A similar problem still exists in the code with distance rather than time, however. If the user enters 6.2AA rather than 6.2 miles, a type-conversion-induced runtime error results from a different procedure in Pace-Pal that calculates the final pace. Because the type of problem present in dealing with time also is present in dealing with distance, more than one fix is needed in Pace-Pal to address all the areas where this type of problem occurs. This, it turns out, is very common in debugging, especially in data-handling code. If you find a bug in one place, check for it in other areas of the program too. If wrong assumptions or coding techniques led to problems once, they very likely will lead to problems again.

If you think you have a problem area that you need to check throughout your application, you should be able to pinpoint it quite easily if it results in an error condition. You can use the techniques presented earlier in this lesson. Insert On Error Resume Next in every procedure. At the end of every procedure, check the Err object and display a message if an error occurred within that procedure. This level of tracing gives you a clear indication of any procedures in which the error occurs.

Creating Your Own Debug Window for Tracing

By now, the value of good tracing should be clear. Good debugging usually comes down to good program tracing. Several approaches to tracing are available. The technique of tracing the flow of every statement by displaying message boxes was presented in the sections "Using Simple Debugging Techniques" and "Using Advanced Debugging Techniques." This method can be a bit cumbersome because it requires interaction with a series of message boxes each time you do a trace. The technique of simply displaying a message box only if an error occurs also was illustrated earlier in this chapter. This approach is quite effective, but you might want to monitor the flow of your program at times even if an error has not occurred.

Tracing all statements, even under non-error conditions, can provide a helpful overall picture of what the code is really doing. Understanding the flow of the code and gaining insight into the state of the variables as the code execution progresses helps you better understand the behavior of a program. The more you understand the overall behavior of a program, the better code you can write for it. Likewise, you'll be able to make better intuitive decisions when chasing problems. So what's the best approach when you want to trace normal program flow? As established earlier, the "always display message box" approach can be rather cumbersome. And the "display message box only after error" approach doesn't give the full level of feedback you might be looking for in every case.

You really need a separate debug window that lets you peek into the program as it progresses, much like Visual Basic 4.0's debug window. It turns out that you can build at least some of those capabilities right into your page with VBScript. You just add a rather large form text area input control at the bottom of your page. A debug text box typically is used as a temporary debug tool, and is removed before you release your final version of the code. But in the meantime, during the script-development phase, it can be a considerable help during debugging. Listing 12.12 shows the Pace-Pal HTML source code with an <INPUT> tag added to define this type of debug text box. The sample program Ppalerr5.htm on the accompanying CD-ROM uses this same approach, but it additionally formats the input controls in a table for better visual presentation on-screen.


Listing 12.12. Adding a form text area input control to capture debug trace statements.

<FORM NAME="frmPace">

<PRE>

<FONT COLOR=BLUE FACE="Comic Sans MS" SIZE=6>

Distance:     <INPUT NAME="txtDistance" VALUE="" MAXLENGTH="5" SIZE=5>

<INPUT TYPE="RADIO" NAME="Dist" CHECKED VALUE="Miles"

onClick=SetDistance("Miles") > Miles

<INPUT TYPE="RADIO" NAME="Dist" VALUE="Kilos"

onClick=SetDistance("Kilos")>Kilos

Time:         <INPUT NAME="txtTime" VALUE="" MAXLENGTH="11" SIZE=11>

in minute:second format

<INPUT TYPE=BUTTON VALUE="Display Pace" SIZE=30 NAME="Calc">

Pace per Mile: <INPUT NAME="txtPaceMiles" VALUE=""

MAXLENGTH="5" SIZE=5>     Pace per Kilo:

<INPUT NAME="txtPaceKilos" VALUE="" MAXLENGTH="5" SIZE=5>



Debug Window: <TEXTAREA NAME="txtDebug" ROWS="10" COLS="60" >

</TEXTAREA>



</FONT>

</PRE>

</FORM>


The textarea control, which is named txtDebug in this example, provides a convenient place to log trace information. You can add code to your script to display debug information in this control wherever you want logging to take place in your program. As a matter of fact, if you want, you can print debug information after each and every script statement. This logging takes place in an unobtrusive manner and doesn't require the interaction of the message box. You can even provide variable and code-location information when you display information in the txtDebug control. Listing 12.13 shows an example of Pace-Pal modified to use this style of tracing.


Listing 12.13. Tracing program flow and variables with form text area input control.

document.frmPace.txtDebug.Value = document.frmPace.txtDebug.Value & _

    "Prior to assignment, vSeconds =" & vSeconds & vbCrLF



' Return total seconds value

ConvertStringtoTotalSeconds = CInt(vHours) + CInt(vMinutes) _

          + CInt(vSeconds)



document.frmPace.txtDebug.Value = document.frmPace.txtDebug.Value & __

    "After assignment, vSeconds =" & vSeconds & vbCrLf


When Pace-Pal is run with these modifications, a clear trace appears in the txtDebug textarea control as the program progresses. Figure 12.13 shows a sample of the trace.

Figure 12.13 : Output from the textarea trace.

This trace can provide great insight into code behavior. Another advantage of storing trace output in the txtDebug control is that you can use this information to review your program history even after your script execution completes. Suppose that your script generates 200 lines of trace information in response to a button click. After the block of code associated with the button click completes, all this information still is available in the txtDebug control. You can scroll through the data and reconstruct what happened to the script by looking at this trace. Notice that the variable vbCrLf is used here. This is declared to contain the standard line-separator characters-specifically, Chr$(10) & Chr$(13).

Building Your Own Tracing Routines

The technique of placing trace statements in a textbox control is handy, but you can make it even more convenient. The statements that log information are not difficult to understand, but they are a bit lengthy. Also, you want to ensure that you take the same approach with every log statement. If you generate one type of trace information in one area of code, and then generate trace information in another format somewhere else in your script, it will be more confusing to analyze the trace results. So it is more convenient to simply call an easy-to-use subroutine every time you want to log trace messages. That way, the subroutine can contain the code to handle all aspects of logging the trace information. Listing 12.14 shows a sample trace debug subroutine.


Listing 12.14. The definition for a simple trace routine.

Sub DebugMsg(Info)

'--------------------------------------------------------

' Print debug message to textarea used for debug display

     Document.frmPace.txtDebug.Value =  _

        Document.frmPace.txtDebug.Value & info & vbCrLf

End Sub


This procedure takes a string, which is provided as a parameter at the time the procedure is called, and adds that string to the current contents of the textbox control. This procedure may be called many times from different locations in the program. The string, which is provided as data to this procedure, should describe the program from which the call is made in order to provide a meaningful trace history. For clear output, new information provided by the trace procedure should be displayed as a new line in the text box. That is the purpose of the vbCrLf constant variable in Listing 12.14. vbCrLf is a variable for the intrinsic VBScript constant containing the carriage-return/line-feed characters that cause a new line to be generated. The assignment statement appends the new information after the existing contents of the textbox control. Then, a carriage return/line feed is appended to the end of that. Any information that follows appears on a new line.

Using this type of subroutine doesn't just save you from repeatedly typing the same trace code. It also ensures that all trace output is generated in a standard, consistent manner. After all, every trace message comes from the same place with this approach. Then the rest of the code simply uses this common subroutine wherever traces are needed. Listing 12.15 shows an example of Pace-Pal, modified to use calls to the trace procedure to carry out a trace.


Listing 12.15. The call to a simple trace routine.

DebugMsg "Prior to assignment, vSeconds =" & vSeconds

    ' Return total seconds value

    ConvertStringtoTotalSeconds = CInt(vHours) + CInt(vMinutes) _

       + CInt(vSeconds)

DebugMsg "After assignment"


Because any expression can be passed for subsequent display to DebugMsg, you have the flexibility to send information about any variable and have that recorded with your program trace. Notice that in the first call to DebugMsg, the descriptive string passed to the procedure contains the contents of a variable as well as indicates the location where the call was made. The benefits of this type of flexible trace are tremendous. You can monitor the changes in variables as your program executes, and monitor the current location and progression of the code. You can gain very keen insights from the program-monitoring this trace procedure provides.

NOTE
You might have noticed that the calls to the debug trace procedure are not indented, unlike the rest of the program. This is a convention that can be used to make temporary calls stand out. All the debug trace calls here are temporary calls only. Normally, you add them just to aid your debugging efforts during design time and remove them prior to releasing your final product. The practice of left-aligning the debug statements makes it easy to spot the statements and remove them later.

Looking at a Sample Variable Analysis Routine

The information provided by the DebugMsg procedure is good, but even that might not tell you everything you want to know. If you're chasing a problem in a script, it might not be enough just to see the program flow. You might even use DebugMsg to display the contents of a variable and find that it still doesn't quite fill you in on the whole story of the state of your program. One other piece of the picture that can be very important is determining what subtype of data a variable represents, as well as the current value of that data.

A variable that prints as 23 in the trace log, for example, may be stored in a variant variable with subtype string, or a variant variable with subtype integer. For some types of problems, it can be very important to understand which subtype data representation a variable currently has. If you write elegant code to look at the variant subtype and interpret it for logging, the code can be rather lengthy. It's certainly not something you want scattered all over your program. Fortunately, you can apply the same trace procedure solution to this problem. An expanded trace procedure can be defined to provide a "power trace." This procedure not only accepts a descriptive parameter indicating the program location, but it also accepts a parameter that contains a variable to analyze. The procedure then logs an informational string to the textbox control based on these parameters. Part of the information logged in the textbox control displays the program location. The other portion reflects the value and subtype of the variable. An analysis of the variable is carried out to determine the subtype of the variant. This type of debugging procedure provides a very detailed and powerful trace history. Listing 12.16 shows an example.


Listing 12.16. The definition for a variant variable analysis routine.

  Sub VarAnalyzeMsg(InfoMsg, VarToAnalyze)

  '--------------------------------------------------------

  ' Print debug info message to textarea used for debug display;

  ' print out type and value of VarToAnalyze



      Dim VarMsg ' Used to build up info about VarToAnalyze



      ' Determine type of variable

      '   Note: If this code was in Visual Basic 4.0, the VB

      '         intrinsic constants such as vbEmpty could be

      '         used instead of the hardcoded values shown

      '         (not defined in beta VBScript)

      Select Case VarType(VarToAnalyze)

         Case 0   ' vbEmpty

            VarMsg = "Empty"

         Case 1   ' vbNull

            VarMsg = "Null"

         Case 2   ' vbInteger

            VarMsg = "Integer, Value=" & VarToAnalyze

         Case 3   ' vbLong

            VarMsg = "Long, Value=" & VarToAnalyze

         Case 4    ' vbSingle

            VarMsg = "Single, Value=" & VarToAnalyze

         Case 5   ' vbDouble

            VarMsg = "Double, Value=" & VarToAnalyze

         Case 6  ' vbCurrency

            VarMsg = "Currency, Value=" & VarToAnalyze

         Case 7  ' vbDate

            VarMsg = "Date, Value=" & VarToAnalyze

         Case 8   ' vbString

            VarMsg = "String, Len=" & Len(VarToAnalyze) _

               & " Value=" & VarToAnalyze

         Case 9   ' vbObject

            VarMsg = "OLE Automation Object"

         Case 10  ' vbError

            VarMsg = "Error"

         Case 11  ' vbBoolean

            VarMsg = "Boolean, Value=" & VarToAnalyze

         Case 12  ' vbVariant

            VarMsg = "Non-OLE Automation Object"

         Case 13  ' vbDataObject

            VarMsg = "Byte, Value=" & VarToAnalyze

         Case 17   ' vbByte

            VarMsg = "Byte, Value=" & VarToAnalyze

         Case 8194 ' vbArray + vbInteger

            VarMsg = "Integer Array, Ubound=" & Ubound(VarToAnalyze)

         Case 8195  ' vbArray + vbLong

            VarMsg = "Long Array, Ubound=" & Ubound(VarToAnalyze)

         Case 8196 ' vbArray + vbSingle

            VarMsg = "Single Array, Ubound=" & Ubound(VarToAnalyze)

         Case 8197 ' vbArray + vbDouble

            VarMsg = "Double Array, Ubound=" & Ubound(VarToAnalyze)

         Case 8198 ' vbArray + vbCurrency

            VarMsg = "Currency Array, Ubound=" & Ubound(VarToAnalyze)

         Case 8199 ' vbArray + vbDate

            VarMsg = "Date Array, Ubound=" & Ubound(VarToAnalyze)

         Case 8200 ' vbArray + vbString

            VarMsg = "String Array, Ubound=" & Ubound(VarToAnalyze)

         Case 8201 ' vbArray + vbObject

            VarMsg = "Object Array, Ubound=" & Ubound(VarToAnalyze)

         Case 8202 ' vbArray + vbError

            VarMsg = "Error Array, Ubound=" & Ubound(VarToAnalyze)

         Case 8203 ' vbArray + vbBoolean

            VarMsg = "Boolean Array, Ubound=" & Ubound(VarToAnalyze)

         Case 8204 ' vbArray + vbVariant

            VarMsg = "Variant Array, Ubound=" & Ubound(VarToAnalyze)

         Case 8205 ' vbArray + vbDataObject

            VarMsg = "vbDataObject Array, Ubound=" & Ubound(VarToAnalyze)

         Case 8209 ' vbArray + vbByte

            VarMsg = "Byte Array, Ubound=" & Ubound(VarToAnalyze)

         Case Else

            VarMsg = "Unknown"

      End Select



      VarMsg = "...Var type is " & VarMsg

      ' Print to textarea used for debug trace, must use vbCrLf

      ' to advance lines

      Document.frmPace.txtDebug.Value = _

         Document.frmPace.txtDebug.Value & InfoMsg & vbCrLf

      Document.frmPace.txtDebug.Value = _

         Document.frmPace.txtDebug.Value & VarMsg & vbCrLf

   End Sub  ' VarAnalyzeMsg


Style Considerations
If you're really alert, you might have noticed that a check is made to see whether the variable is represented in several storage types that VBScript does not support. These include currency and arrays of nonvariants that are supported by Visual Basic. Because it doesn't hurt to check for these extra types, and it could even provide added insight if there was an internal VBScript error that resulted in a bad type, these checks are left in here. This also makes for upward-compatible code that can be ported to VBA or Visual Basic 4.0 programs without change.

Listing 12.17 shows a modified sample of the familiar Pace-Pal example. Pace-Pal has been modified to make calls to the VarAnalyzeMsg routine. These calls have been added both before and after the statement that earlier samples indicated was the problem statement. Because three variables are involved in the problem statement (vHours, vMinutes, and vSeconds), all three should be inspected prior to the problem statement to help determine the cause of the problem. Therefore, three different calls to VarAnalyzeMsg are used-one to analyze each specific variable. Likewise, the same three calls to VarAnalyzeMsg are made after the problem statement. This is to ensure that none of the variables has unexpectedly changed value or subtype.

NOTE
You can pretty well determine by looking at the code involved in the incorrect section of Pace-Pal that no variables will be changed after the problem statement. The post-statement calls to VarAnalyzeMsg, however, ensure that you are not making any mistaken assumptions about values not changing. This is a good standard debugging practice to follow, and the calls are included here to illustrate that point. You should scientifically verify the contents of variables during debugging rather than making potentially faulty assumptions. If you've decided that a full trace is in order, you can never assume that a value will not change. It is always best to check debugging information before and after a given statement. Even if you think that nothing will have changed, there is always a chance that you're wrong, and the extra debugging procedure costs you only the time it takes to enter it.


Listing 12.17. The call to the variant variable analysis routine.

Call VarAnalyzeMsg("Analyzing vHours prior to ConvertString",vHours)

Call VarAnalyzeMsg("Analyzing vMinutes prior to ConvertString",vMinutes)

Call VarAnalyzeMsg("Analyzing vSeconds prior to ConvertString",vSeconds)



    ' Return total seconds value

    ConvertStringtoTotalSeconds = CInt(vHours) + CInt(vMinutes) + _

        CInt(vSeconds)



Call VarAnalyzeMsg("Analyzing vHours after call to ConvertString",vHours)

Call VarAnalyzeMsg("Analyzing vMinutes after call to ConvertString",vMinutes)

Call VarAnalyzeMsg("Analyzing vSeconds after call to ConvertString",vSeconds)


NOTE
The source file for the Pace-Pal program modified to contain the change shown here is available on the accompanying CD-ROM in the file Ppalerr5.htm.

The modified Pace-Pal program with the VarAnalyzeMsg trace statement generates the output shown in Figure 12.14.

Figure 12.14 : Output from the variant variable analysis routine.

The txtDebug text box trace area is filled with meaningful trace information that can help you understand the behavior of the program. Although this trace facility might not be as powerful as the trace capabilities built into other languages, such as Visual Basic 4.0, it does give you ample power to get to the root of just about any VBScript-generated error.

Looking at More Reasons Why VBScript Can Be Tough to Debug

As if all these debugging challenges weren't enough, there are still a few more that haven't been discussed yet. It's important to be aware of these additional challenges-not so that you will spend sleepless nights worrying about them, but so that you will have a broad view of what to expect as you start to chase VBScript-related problems.

VBScript is what sometimes is called a glue language. It is great at gluing many components together to provide a powerful programmatic interface. You easily can weave a masterpiece of ActiveX controls, Java applets, intrinsic form controls, and OLE automation components into one tapestry when building your script. Opening the door to such easy integration of components, however, also opens the door to potential problems with them. A third-party control might have a bug in it. The Java applet provided by your coworker might be riddled with hideous errors in logic. The possibilities for problems are endless. And if your VBScript program incorporates those pieces, the problems will be visible to the user through your program. When such a problem occurs, the user considers it your script's problem. Then it falls to you, the debugger, to isolate areas of code to prove that a problem is caused by one specific component.

And now the good news that will save you from those sleepless nights: The very same skills discussed in this lesson that will help you hone in on VBScript errors also will help you hone in on component-related errors. You still need to trace through the program, isolating the problem to one specific area of the code. You still might need to display the value of variables, or even component property values, to monitor the state of the program before and after the statements related to the components that might be causing the errors. You still might need to break down one large VBScript statement involving components into a series of smaller statements to isolate the problem. In every case, the same tools and techniques already discussed still apply.

The Moral of the Story

The best advice to give for debugging and error handling is, "Don't make mistakes!" The outcome is no more probable than if you told the sun to rise in the north and set in the south, though. Mistakes are an inherent and unavoidable part of today's programming model. If you're a programmer, you will make plenty of them. A big part of the task of producing VBScript programs is getting out the bugs and making your programs shield the user from the bugs. The real moral of the story, then, is to apply these debugging, tracing, and error-handling techniques vigorously. Make use of debug tracing and variable analysis routines like those provided in this chapter. They will add efficiency and consistency to your debugging. Keep your eyes open for good debugging tools. VBScript debug tools currently are far behind those for Visual Basic 4.0, but this language is still in its infancy, and you can expect support to increase. For now, scripts may take longer to debug than programs in other languages due to the lack of tools. But with patience, strategy, and some of the techniques discussed in this chapter, your debugging sessions still can be highly effective.

Review

This chapter provided important debugging techniques that can be applied to syntax and semantic errors. Syntax errors and semantic errors, both caused by incorrectly specifying your programs, were defined-and examples of both were illustrated. This chapter also demonstrated how VBScript provides the means to turn off the automatic generation of runtime error messages to the user and prevent the subsequent script termination. Tracing techniques can also be helpful in analyzing all types of errors. The various tracing techniques were summarized in this chapter.

If you're doing a lot of debugging, you'll probably want an even more powerful debug repertoire. You can obtain this by building your own debug routines. This chapter supplied several strategies you can incorporate in your own Web pages to analyze and prevent errors from occurring. While VBScript does not have a powerful set of debug tools like Visual Basic 4.0 does, there is enough in the language to craft your own effective debugging approach. The most important tools, however, are the same ones you supply for any language you must debug-your own persistence and patience.

In Chapter 13, "Dynamic Web Page Building," you will learn how to use Internet Explorer's Document object to create Web pages that dynamically alter the content of a Web page. You will also learn other techniques, such as writing the current date and time to a Web page, or dynamically changing the colors on a page. These techniques are very useful and helpful in VBScripting arsenal.