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, extreme amounts of logical, objective analysis must be applied to scientifically pinpoint the bug and prove its presence. The process of bug-hunting, therefore, often requires a special focus and concentration seldom encountered in other aspects of life. Likewise, hunting complex software bugs can cause a special frustration seldom encountered in other aspects of life! The good news is that a good set of problem-solving techniques and top-notch tools can make this process much easier. The not-so-good news is that top-notch tools do not come with VBScript. However, the tips and techniques suggested here go a long way toward minimizing debugging pitfalls.
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 you'll learn more about those capabilities and what debug tools VBScript lacks, but first, let's examine what VBScript does provide.
Errors come in a variety of shapes and sizes. The easiest types to picture and correct are simple typing or language usage errors in your Visual Basic code. For example, consider what would happen if you mistakenly typed this:
Dimwit C
instead of this:
Dim C
to declare a variable named C. Because the word Dimwit is not part of Visual Basic's language or syntax, the first statement can't be processed. This kind of error is commonly called a syntax, or syntactical, 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! 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 during a user's interaction with your script, or worse, give your user a bad result that he's not even aware of!
So how does a script go on strike? Suppose you have a statement like the following:
Dim c, c, c
This statement, too, contains a syntax error, but this error consists of an illegally duplicated definition rather than an incorrectly typed keyword. This statement is not legal according to the rules of VBScript because a variable name can only be used once; here, it has been defined three times in a row. When you attempt to load the page containing this program into the browser, you'll be greeted with the message shown in Figure 17.1.
Figure 17.1 : A syntax-checking script error message.
This particular error was identified as soon as the page was loaded in the browser, but other syntax errors might not be caught until after the Web page is loaded. Suppose, for example, that a certain code statement is only carried out when a specific button is clicked. In the script event handler routine that is associated with that button, after a series of other calculations, you have a statement like this:
a = b / c
Assume that c is computed
internally prior to the calculation, and c's
value varies from one calculation to another. This statement may
work perfectly well for the first several times the button is
clicked. However, if the value of c
ever turns out to be 0, this
statement will fail. Because the computer is unable to divide
by 0, VBScript will generate
a message box similar to the one in Figure 17.1. This will bring
your program to a screeching halt.
Note |
When you are presented with the run-time error message from the Internet Explorer browser, you are given a check box option to suppress notification of future run-time errors. If you check this box, labeled Ignore, further script errors on this page, notification of future errors in other scripts on the page will be suppressed. Loading of the specific script that caused the problem will still be halted when errors occur. |
These are just two examples of syntax errors that can be detected
when VBScript tries to run your program. These various error conditions
are called run-time errors. Hopefully, your user never
sees any run-time errors. Ideally, you would write perfect, error-free
code! However, given the complexity of programming, the odds of
producing a perfect program are slim, so you must be able to thoroughly
test your programs to remove all problems before you turn them
over to your users. Also, you can take steps when writing your
code to make it more robust if a run-time error should occur.
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, there is no standard VBScript interactive development environment from Microsoft to make the debugging task easier. This makes the task of error-proofing your programs a considerable challenge. However, VBScript does provide some help in recovering from errors and pinning them down. You can write code that helps a program robustly continue after an error has occurred. 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 out error information or takes a code path based on an analysis in code of what error has occurred. These techniques for dealing with run-time errors are detailed in the section "Effective Error Hunting" later in this lesson. But first, let's examine another category of error.
By now you may 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, the VBScript support for run-time errors may help you handle them, but it won't 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 intend). For example, suppose 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 was used here in place of .04. The result is that this incorrect statement won't add 4 percent to your total sale, but it will add four times the cost of your item to your total sale! This is clearly an error. However, as far as the VBScript interpreter can tell, 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 is not able to automatically highlight these for you. Rather, after noticing an incorrect result, you must work backward until you hone in on the problem. Semantic problems do not directly cause run-time errors; they just lead to bad results. And while bad results may suggest that you have a problem, they won'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 semantic errors will be discussed in the next section, and you'll get a closer look at run-time error handling techniques that can be used to tackle syntax errors. For now, recognize that it is not too important that you know the textbook description of semantic versus syntactical errors. It is important, however, that you are aware of the techniques available for dealing with them. You should also realize that the most important tools for debugging-patience and persistence-are provided by you.
Now that you have a feel for the type of error support in VBScript, it's time to observe it in action. The Pace-Pal program, introduced briefly on Day 2, "The Essence of VBScript," serves as our first case study. Pace-Pal is the running pace calculation program shown in Figure 17.2. It is available in file pace-pal.htm on the CD-ROM. Like the other programs you've learned about in this book, this program consists of a standard HTML Web page with embedded VBScript. Pace-Pal allows the user to specify a distance in either miles or kilometers, and a time in minutes/seconds format (hours can also optionally be provided if you're willing to run that long!). With this information, a pace per mile can be calculated. For example, if you ran a 6.2-mile race (10k) in 37 minutes and 12 seconds and supplied that information to Pace-Pal, Pace-Pal would calculate that you averaged 6-minute miles.
Figure 17.2 : The Pace-Pal program with bad input data.
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 in 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. The poor VBScript interpreter is left holding the bag, asked to carry out a statement that makes no sense and will lead to a wrong result! Needless to say, the interpreter balks, tossing up the famed run-time error window. A picture of the run-time error window generated when Pace-Pal attempts to process a time of 37:12AA is shown in Figure 17.3.
Figure 17.3 : A run-time error in the Pace-Pal program.
VBScript is nice enough to clue you in to the problem. The error message that is 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 since it has detected problems there. If you go back and specify good input and click the Pace button, nothing happens. Until the page is reloaded, VBScript will consider this a bad script and won't process any of it.
As you saw earlier in this lesson, VBScript does provide you with location details for some types of 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 was the case with the error shown in Figure 17.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.
For example, take a look at the code in Listing 17.1. 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 contained on the CD-ROM under pace-pal.htm. The CD-ROM also contains a main viewer page, default.htm, that provides an overview for each day's material with an easy index to all programs. This is the recommended mode for viewing the pages discussed today. |
Listing 17.1. An insect lives here-buggy code!
Function ConvertStringToTotalSeconds (ByVal sDuration)
'--------------------------------------------------------------------------
' Takes HH:MM:SS format string and converts to total seconds
Dim iPosition 'Position of ":" separator
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 on 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)
seconds = 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 being a slow, tedious way to solve problems. Fortunately, there are better, easier, more precise ways to hunt down the error.
The debugging difficulty presented by Pace-Pal is that you can't tell where things start to run amok. As a matter of fact, "start" to run amok is rather misleading. Things really go amok all at once, with no gradual transition, because VBScript treats any run-time error as fatal! So the first step is to hone in on our 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.
However, if you've debugged in other environments, one obvious tool to pinpoint the rogue statement may come to mind-the MsgBox function. You learned about the MsgBox function on Day 14, "Working with Documents and User Interface Functions." When this function is encountered, the designated message will be displayed to the user, and the flow of your program halts until the user clicks on the message box button to acknowledge the message and proceed.
That means that the MsgBox function 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 run-time error. Suppose you're chasing an error in your program. You insert the following statement in the middle of your program and re-run it:
MsgBox "I made it this far without my program choking!"
If your program displays that message when you re-run it, you have some additional insight into the error you're chasing. You know the run-time error was not caused by any statement preceding the MsgBox function call. The error lurks in some statement after that point in your code. For the next step, you could shift the MsgBox statement down a line and re-run the test. If that works, do it again. And again. And again. Until you hit the line that causes the run-time error.
Or you could take the tried-and-true "narrow it down one step at a time" approach. You put your MsgBox function halfway through your code and see if 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 run-time error. If the flow of code is not sequential, the process of isolating the problem is even tougher. For example, 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 gets very difficult, not to mention complex!
If both of these approaches sound similarly tedious and time-consuming, that's because they are! You could save yourself a few test runs by starting right out with a MsgBox statement after each and every statement, each with a different message. For example, assume 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 could then modify it to get a MsgBox-based program flow trail:
sub Test_OnClick
dim a, b, c
MsgBox "Point 1"
a = text1.text * 3
MsgBox "Point 2"
c = text2.text * 4
MsgBox "Point 3"
d = a + c
MsgBox "Point 4"
end sub
If your program runs and then dies with a run-time error, the MsgBox statement that was the last to display on your screen will tell 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.
It would be nice if a program could simply continue after it caused a run-time error. That way, at some later point in the script, you could use code statements to learn if the end of a procedure was successfully reached, or to print out the values of variables for you to inspect, or to show you the result of calculations. If only a program had the perseverance to forge on after it hit rough waters so 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 run-time error. Although VBScript is trying to save you from bad data or results when it produces a run-time error, there are cases where you might prefer to continue execution after the error occurs, even if it means living with bad results. For example, maybe your program calculates a runner's calories burned, the amount of shoe rubber rubbed off, and the fluid ounces of sweat produced based on distance as well as pace. If so, it could be quite annoying to your user to halt the whole program just because he entered one piece of information incorrectly.
A real-life situation can serve as an analogy, at least for parents, aunts, uncles, and babysitters. Assume you have asked a six-year-old to clean her toy-strewn room. After a little complaining, she tackles the task; meanwhile, you finally settle down in the armchair to read the latest issue of The Visual Basic Programmer's Journal. Just as you become immersed in an article, you hear little, rapid footsteps. "You can't be done already!" you exclaim.
In response, the youngster replies, "I can't clean anymore. My dolly box is at Grandma's!" The youngster's mind has just experienced the equivalent of a fatal run-time error. She's encountered a situation she does not know how to handle. She is letting you know that due to the perceived direness of the situation, the whole task must be aborted.
As she skips out the door to play, you shout after her in frustration, "Whoooaaa there! What about the stuffed monkey, box of crayons, 57 marbles, and your collection of paper doll clothes scattered all over?! You could've picked those up!"
Needless to say, in programming as well as room-cleaning, there are certainly times when this abandon-ship philosophy is not optimal. If you wish to perform additional debugging after the problem statement, or if you want to beef up your program, you need a way to override the abort at the first sign of trouble. 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 on with the next statement. An example of this statement applied to the Pace-Pal problem procedure is shown in Listing 17.2.
Listing 17.2. Code made more robust with an On Error statement.
Function ConvertStringToTotalSeconds (ByVal sDuration)
'--------------------------------------------------------------------------
' 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 ":" separator
Dim vHours ' Number of hours required
Dim vMinutes ' Number of minutes required
Dim vSeconds ' Number of seconds required
On Error Resume Next
Note |
The modified Pace-Pal program with the change shown here is contained on the CD-ROM under ppalerr1.htm. |
When the On Error Resume Next statement is used, you don't get any scary messages of gloom and doom, and your program doesn't come to a screeching halt. Instead, the VBScript interpreter keeps on chugging with the next statement, leaving your user none the wiser. The results from running Pace-Pal with this modification are shown in Figure 17.4. You'll 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 17.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 room-cleaning analogy serves to illuminate this problem as well. Assume you are once again starting your young helper on a room-cleaning task, and this time you are determined to make the ground rules clear. You provide some specific direction: "Go clean your room. I don't care what happens, I want you to plug away until you've taken care of every last thing. If you run into problems, I don't want to hear about it!" The cleaning task seems to be going well, until you notice water seeping out from under the door. When you go to inspect, the grin-ning child, knee deep in water, points to a ruptured water pipe in the ceiling. Just as you start to ask, "Why didn't you tell me?" you realize the answer to your question: You asked her not to.
The On Error Resume Next statement gives you an analogous mechanism to ignore errors, as the Pace-Pal sample demonstrates. Unfortunately, when you use this statement, you run the risk that you won't find out about a program problem that you really do care about. For that matter, unless you know specifically what caused an error, it is rarely safe just to ignore it.
If you've used other languages with error handling, particularly VBA or Visual Basic 4.0, you may realize that most error handling systems provide even more error handling flow-control capabilities. 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 would direct 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. If an error occurs in a procedure while this statement is in effect, your program will simply move on to the next statement in the procedure. Once again, though, the VBScript language comes to our rescue. There is a way to use On Error Resume Next wisely. You must couple it with the power of the err object.
If you combine On Error Resume Next with a special VBScript object called the err object, your program can not only survive run-time errors, it can even incorporate program logic that analyzes them 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. The err object analysis takes place to see what kind of problem, if any, has occurred within a body of code. You can display the problem error code and description after an error occurs, and still let your program continue on for further debugging after you display this information. You may even 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 could prompt the user to re-enter the data.
The err object is an intrinsic VBScript object. That means 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 have been detected. Any other value represents an error code. If you do find an error code, the err.description field will provide the standard text description of the error. This message is the same one that would pop up on the run-time message error box when the program screeched to a halt if you weren'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 will be the name of your VBScript file itself if it was your VBScript code that caused the problem. In some cases, however, you may 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 17.3 shows one example of how you might check to see if an error has occurred.
Listing 17.3. Checking the err object to see if 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
Code analysis of the err object like that shown in Listing 17.3 can also be applied to debug problems like the Pace-Pal situation. If On Error Resume Next is used to turn off run-time error reporting and aborting, you can look at the end of the suspect procedure to see if any errors occurred within it. If errors did occur, you can use the err object to print out full details. One important consideration with this approach, however, is that if more than one error has occurred within the procedure, you will see only information 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 if there were multiple errors. Listing 17.4 shows an example of the Pace-Pal code with a check inserted at the end of a procedure.
Listing 17.4. Code with additional error diagnostics from the err object.
vminutes = left(sDuration,iPosition - 1)
seconds = 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 contained on the CD-ROM under ppalerr2.htm. |
Notice that this code will print out information only if the error occurred. If the error did not occur, the user will not be disturbed by error information. The results of this error check are shown in Figure 17.5. As the figure shows, the error check added to the Pace-Pal code does detect an error within the procedure. The cause is clearly identified as a type conversion error, and the source is pegged to be the script itself.
Figure 17.5 : Information about the error.
Bolstered by the err object checking, 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 since there is not valid data to work with. You have more insight because you can be certain that an error has 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. We'll turn next to a few more advanced details of the err object and the On Error Resume Next statement, followed by a look at more tracing techniques, before we return to the quest for our Pace-Pal bug.
The On Error Resume Next statement, also called an error handler, is a procedure-level statement. It only remains in effect 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 run-time error, halting the program and displaying the run-time error to the user. However, if a higher-level procedure with an On Error Resume Next function were calling a lower-level procedure with no error handling, then when the lower-level procedure causes the error, it would be addressed by the higher-level procedure.
This is commonly called "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.
Once 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 will be halted. The bottom line is that if you want to use the err object to carry out any error analysis in a procedure, make sure an On Error Resume Next first appears within that procedure.
The err object itself has a couple more interesting capabilities you have not yet learned about-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 could 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 calling and current procedures has an On Error Resume Next statement, the program will flow to the next applicable statement. In such a case, the err object will then contain the appropriate information for the error that was raised. For example, err.number will equal 13. On the other hand, if there is no On Error Resume Next in the active lineup of calling and current procedures, the program will treat the raised error as a regular run-time error, displaying a message to the user and terminating the program.
You may be thinking that it would take a pretty twisted programmer to purposely inject a simulated error into his code. There are some situations where such a tactic is warranted, however. (Naturally, the VBScript development team at Microsoft wouldn't have included this method if it could only be used 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 may write code that inspects err.number to determine if 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 may decide to write code that continues on 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 will flow 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 was 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 will no longer 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 will be 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 will still find the old type conversion error indicator for the error that was already analyzed, even if the most recent statement caused no error.
If you make use of err.number multiple times within a procedure, you'll want to ensure you're not reacting to leftover error data. Fortunately, VBScript provides a means to do this: the err.clear method. This method will reset all fields of the err object, and will assign err.number back to 0 to indicate no error. So when you want to make sure 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 err.number and carrying out any handling needed.
Many error-handling strategies can be built on On Error Resume Next and the err object. If you don't use On Error Resume Next at all, your run-time 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 may still 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 you may need to insert statements that give you more details about an error to pinpoint the cause of it. Fortunately, further hand-crafted techniques are available to trace and understand your code. So next you'll learn about what tracing code is really all about.
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 might also 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. Since you know where you inserted the MsgBox calls, you can easily follow the progression of the code. There are, however, more powerful, elegant ways to trace code. 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 in conjunction with 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. Before moving on to more sophisticated tracing methods in VBScript, however, let's consider the debug and tracing mechanisms of some other environments to put the VBScript capabilities in perspective.
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 debug features of Visual Basic 4.0. Although such tools are expected to materialize over time, at this date they do not exist. 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 can sometimes help check syntax as you type the programs in. 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 debug facilities. While you can't use these debug facilities directly with VBScript, you can apply some of the same concepts with the "build-it-yourself" trace techniques you'll learn about soon.
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. For example, Visual Basic 4.0 allows you to stop the program at any location by simply 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, allowing 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 lets you 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. A view of the Visual Basic 4.0 development environment, with a suspended program that has reached a breakpoint, is shown in Figure 17.6.
Figure 17.6 : The Visual Basic 4.0 debug environment.
Once you are done checking 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 can also 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 is stopped. And if you're even nosier about what is going on, you can make arrangements to automatically monitor the contents in your favorite variables as the program chugs along. As variable values change, the new values are automatically displayed 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 is probably 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 debug features are available to you as you develop VBScript programs. There is no inherent means 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, there are several rays of encouragement to
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. There are also many utilities available for various aspects of Web page authoring available at the Microsoft site under www.microsoft.com/intdev. |
If you happen to be one of the million plus Visual Basic 4.0 or VBA programmers, you have a secret weapon that you can use in developing VBScript code. Try writing the code in Visual Basic first, 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, once it is stable, you can move it to VBScript.
A note of caution, however: There's no such thing as a free lunch, and there's also no such thing as a free debugger (or at least so it seems). There are language differences 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 Visual Basic for Applications, 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 may not be immediately obvious, even if you know both languages fairly well. Day 20, "Porting Between Visual Basic and VBScript," provides a detailed look at these differences. The bottom line is that if you use the "debug in Visual Basic first" approach, don't think you're home free after you get the program working there. Porting work may still lie ahead in moving the code to VBScript.
So it's clear that the king of the debugging realm is Visual Basic 4.0. To put our view of VBScript into perspective, it's worthwhile to consider the low end of the debugging spectrum-HTML itself. One thing HTML has going for it is a rich set of tools that can aid in quickly developing well-structured pages. But if you don't have one of those tools, or have a low-end tool, debugging HTML itself can be the cause of serious hair pulling and grimacing. Consider the HTML shown in Listing 17.5, and note the <A that marks the beginning of an anchor reference.
Listing 17.5. 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 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 17.6.
Listing 17.6. 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 17.7. Not only are they ugly, but they result in a Web page that doesn't work as intended, with a nonfunctioning link. HTML has no run-time sequence of logic to step through to help pinpoint the error. Rather, it is just a markup or page-generation instruction set. In the absence of sophisticated authoring tools for HTML, you are stuck with the old visual inspection mode of debug. You must look at the page, visually scan it, and review each tag for proper syntax one by one.
Figure 17.7 : Pace-Pal with a missing tag.
It is important to recognize that VBScript presents a whole different debug model than HTML, requiring a different mindset and approach. If you come from a Visual Basic background, this comes as no surprise. You simply have to downscale your debug support expectations from Visual Basic 4.0 for the VBScript environment and realize that you'll need to build with source code statements much of the trace capability that was automatically there in Visual Basic 4.0. On the other hand, if you come from an HTML background without extensive programming experience, it is important to recognize that the old visual inspection and trial and error methods that may serve you well in HTML debugging will not suffice in tracing through more sophisticated programmatic logic or syntax problems. In either case, the hand-crafted VBScript trace strategies that follow can serve as a fundamental, timesaving part of the debug process.
Today's lesson has talked a lot about what VBScript can't do in terms of trace debugging. Now let's talk about what it can do. As you learned earlier in the lesson, a tried and true approach to following the flow of a program is to simply insert standard message boxes to help trace where your program is going. This 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 is an easier way to get the same kind of tracing payback. First, for simplicity, let's consider the case of a trace with just one area of message box feedback.
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. We've already covered both halves of the equation; now we 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, you also have ready access to error information through the err object. This object tells you if an error has occurred. The message box gives you an easy way to display such 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 only displaying trace information if an error has actually occurred. You achieve this by placing a pair of message box statements around the suspected bad line of code. When that 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 17.7.
Listing 17.7. 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)
seconds = 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 17.8. 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.
Figure 17.8 : Output from the message box trace.
Note |
The modified Pace-Pal program with the change shown here is contained on the CD-ROM under ppalerr3.htm. |
The example in the previous section shows the use of many trace statements in just one area of code. In some cases, you may find that inserting one or more trace statements and moving them around is an effective strategy. For example, if you have a 100-line script but suspect that the problem is somewhere in the first 5 lines, 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 it comes right after line 4, and so on.
This type of trace statement movement is actually quite typical of debug 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 may find that much of your debug 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 debug time. Take careful stock of the tools you have available, and find a process that works well for you.
For example, 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 beta Internet Explorer 3.0 menu to launch Notepad. Then you can use Notepad to modify your script and save it. Once 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 selecting the f5 function key. Once 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 with this method.
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, since debugging, and particularly tracing, is a often a tedious, repetitive process. As more tools become available, choosing the right tool is likely to become more and more critical.
Because technology and toolsets are evolving on almost 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. Appendix B, "Information Resources," summarizes many good places to visit to get the latest tool information.
Using a single pair of message box trace statements may 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 may be just as easy to insert a trace after each statement. That way, you are virtually guaranteeing 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 would do you little good to add 200 trace statements to a program if they all said just Error has been detected!. If you ran the program and only Error has been detected! popped up on your screen, you'd 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 will quickly lead you to the corresponding
location in the program. Listing 17.8 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 contained on the CD-ROM under ppalerr4.htm. |
Listing 17.8. 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!"
seconds = 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 run with the same 37:12AA program input presented earlier is shown in Figure 17.9. From reading this trace message and looking at Listing 17.8, 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.
Figure 17.9 : Output from tracing with sequential message boxes.
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, typically requiring a look at the variable contents if variables are involved in the rogue statement. One way to get this information is to print out the contents of the variables and the variable subtypes located right before the problem statement. This technique is applied to the Pace-Pal program in Listing 17.9.
Listing 17.9. Tracing variable contents with 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 ' End of function ConvertStringtoTotalSeconds
The results of this variable trace are shown in Figure 17.10. 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, confusion may remain over exactly what causes the error. There is yet one more step that can be taken to shed light on the problem.
Figure 17.10 : Output from the message box variable trace.
The problem has now 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 if 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. Shown in Listing 17.10 is the modified Pace-Pal script with the problem statement decomposed into smaller pieces that can be individually traced.
Listing 17.10. The complex statement broken apart 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 ' End of function ConvertStringtoTotalSeconds
When the script is run after this change, the message box specifically highlights which piece causes the error. Figure 17.11 illustrates that the Cint function, when applied to a string that contains non-numeric characters, causes VBScript to generate an error. This is the cause of the original run-time calamity first encountered at the start of the lesson.
Once you know the cause of the bug, fixing it is usually easy. First, decide what type of fix you want to put in place. In most programs, like Pace-Pal, many solutions are available. For example, Pace-Pal could immediately check that data entered by the user is in the correct format, and demand that the user re-enter the data if it is invalid. Or Pace-Pal could check the data and drop illegal extra characters without telling the user. Or Pace-Pal could simply 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. Since this is a legal numeric representation, the type conversion problem is avoided and no error occurs. This solution is shown in Listing 17.11.
Listing 17.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!", _ vbOKonly,"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, vbOKOnly, "Error in _
ConvertStringtoTotalSeconds!"
end if
End Function ' End of function ConvertStringtoTotalSeconds
With this fix, the program will be robust enough to continue even if a user enters invalid input. The resulting message box is shown in Figure 17.12. The program will inform the user of the error, substitute a time of 0 in place of the input, and calculate a pace of 0 seconds per mile.
Figure 17.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. Since 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 may 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. However, a similar problem still exists in the code with distance rather than time. If the user enters 6.2AA rather than 6.2 miles, a type-conversion-induced run-time error will result from a different procedure in Pace-Pal that calculates the final pace. Because the type of problem present in dealing with time is also 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 print out a message if an error occurred within that procedure. This level of tracing will give you a clear indication of any procedures where the error occurs.
By now the value of good tracing is probably 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 earlier in today's lesson. This method can be a bit cumbersome since it requires interaction with a series of message boxes each time you do a trace. The technique of simply printing out a message box only if an error has occurred was also illustrated earlier in today's lesson. This approach is quite effective, but there may be times when you want to monitor the flow of your program even if an error has not occurred.
There are many reasons why 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 will help 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 we've already established, 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 may be looking for in every case.
What you really need is 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 you can build at least some of those capabilities right into your page with VBScript. You just add a rather large form textarea input control at the bottom of your page. Terminology for this type of control varies, but since it is similar to the text box control of Visual Basic 4.0, we'll refer to it as a text box here. A debug text box is typically 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 17.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 CD-ROM uses this same approach, but additional formats the input controls in a table for better visual presentation on-screen.
Listing 17.12. Adding a form textarea 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 text area control, which has been 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. An example of Pace-Pal modified to use this style of tracing is shown in Listing 17.13.
Listing 17.13. Tracing program flow and variables with form textarea 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 text area control as the program progresses. A sample of the trace is shown in Figure 17.13. 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. For example, assume 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 will still be 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 according to the conventions described on Day 4, "Creating Variables in VBScript."
Figure 17.13 : Output from the textbox trace.
The technique of logging trace statements to a text box 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 would be more convenient to simply call an easy-to-use subroutine every time you want to log trace messages. That way, the subroutine could contain the code to handle all aspects of logging the trace information.
An example of a trace debug subroutine is shown in Listing 17.14. This procedure just 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 text box 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 seen in the listing. 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 text box control. Then, a carriage return line feed is appended to the end of that. Any information that follows will appear on a new line.
Listing 17.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
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 makes use of this common subroutine wherever traces are needed. Listing 17.15 shows an example of Pace-Pal, modified to use calls to the trace procedure to carry out a trace.
Listing 17.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 may 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 debug 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 on. |
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 may not be enough to just see the program flow. You might even use DebugMsg to display the contents of a variable, and find 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.
For example, a variable that prints out as 23 in the trace log may be stored in a variant variable with subtype string, or a variant variable with subtype integer. You learned about variant subtypes on Day 4. For some types of problems, it can be quite 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 would 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, it also accepts a parameter that contains a variable to analyze. The procedure will then log an informational string to the text box control based on these parameters. Part of the information logged in the text box 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 debug procedure provides a very detailed and powerful trace history. An example is shown in Listing 17.16.
Listing 17.16. The definition for a variant variable analysis routine.
sub VarAnalyzeMsg(InfoMsg, VarToAnalyze)
'--------------------------------------------------------
' Print debug info message to textarea used for debug display, and
' printout 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 hardcoded values
' (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
Note |
Style Considerations If you're really alert, you may have noticed that a check is made to see if the variable is represented in several storage types that VBScript does not support. These include currency and arrays of nonvariants. It's true that VBScript variants do not represent these subtypes of data. However, the VBScript documentation indicates that the VarType can return values for any of these types. This is probably just a carryover from VBA and Visual Basic 4.0, which do support these additional types. Since it doesn't hurt to check for these extra types, and since it could even provide added insight if there were 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. |
A modified sample of the familiar Pace-Pal example is shown in
Listing 17.17. 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 showed was the problem statement. Since there
are three variables 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 Pace-Pal problem area that no variables will be changed after the problem statement. However, the poststatement calls to VarAnalyzeMsg 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 debug 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 debug information both 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 debug procedure costs you only the time it takes to enter it. |
Listing 17.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 CD-ROM under ppalerr5.htm. |
The modified Pace-Pal program with the VarAnalyzeMsg trace statement generates the output shown in Figure 17.14. 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.
Figure 17.14 : Output from the variant variable analysis routine.
Much of the focus of today's lesson has been on tracing your program to eliminate a bug. The debug code changes made to trace code are typically removed before you make a final version of your Web page available to your users. The On Error Resume Next and err object techniques illustrated, though, have some purposes in final non-debug code-based pages as well. They are essential if you are trying to make the final version of your Web pages as robust as possible for your end users. The worst thing you can subject a user to is a cryptic run-time error. It is much better to insert code that tells VBScript to proceed past the errors without alarming your user. On Error Resume Next and the err object can be used to accomplish this.
You can add code statements to check for errors in every procedure and to analyze what type of error occurred using the err object. Based on the type of error, your code can take many different approaches that will make things easy on the user. Code can hide errors from users if the errors are minor. You might choose not to inform the user of an internal VBScript error if it is something you can correct in code and if the user does not need to know about it. If the error was caused by user data, as was the case with Pace-Pal, you might ask the user to re-enter some input or repeat an action. In other cases, there may be no recovery path, but at least you can present the user with a more friendly, explanatory message than that which VBScript would produce. Likewise, you can gracefully bring the script to a halt rather than have it abort on the user immediately as it would if VBScript handled the error directly. The end result in all these cases is a higher quality of scripts for your user.
But wait, there's more (as if it weren't tough enough already)! We've got no debug environment; we've got a language where a multitude of run-time problems, such as type conversions, are waiting to strike if you don't use it just right! Just as you're about finished with the day, you hear, "But wait, there's more!" There are still a few more challenges to debugging VBScript that haven't been covered yet. It's important to be aware of these additional challenges, not so you will spend sleepless nights worrying about them, but so you will have a broad view of what to expect as you start to chase VBScript-related problems.
VBScript is what is sometimes called a "glue" language. It is great at gluing many components together to provide a powerful programmatic interface. You can easily weave a masterpiece of ActiveX controls, Java applets, intrinsic form controls, and OLE automation components into one tapestry when building your script. However, opening the door to such easy integration of components also opens the door to potential problems with them. A third-party control may have a bug in it. The Java applet provided by your co-worker may be riddled with hideous logic errors. 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 will also 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 may need to display variable values, or even component property values, to monitor the state of the program before and after potential component-related problem statements. You still may need to decompose one larger 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 best advice to give for debugging and error handling would be, "Don't make mistakes!" However, the outcome is no more probable than if you told the sun to rise in the north and set in the south. Mistakes are an inherent and unavoidable part of today's programming model. If you're a programmer, you will be making plenty of them. A big part of the task of producing VBScript programs is getting the bugs out and making your programs shield the user from the bugs. The real moral of the story, then, is to apply these debug, trace, and error-handling techniques vigorously. Make use of debug tracing and variable analysis routines like those provided in this lesson. They will add both efficiency and consistency to your debugging. Keep your eye open for good debugging tools. VBScript debug tools are currently 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 lesson, your debugging sessions can still be highly effective.
Today's lesson provides important debugging techniques that can be applied to syntactic and semantic errors. Syntactic errors are problems caused by incorrectly specifying your programs. Syntactic problems typically cause run-time error messages, and therefore are often relatively easy to spot and address. Semantic errors arise when you incorrectly lay out your program logic. These do not cause run-time errors, but rather just lead to bad results. Spotting and correcting semantic errors can be more of a challenge in many cases.
There are several key features of the language that specifically aid in dealing with errors:
VBScript provides a means to turn off the automatic generation of run-time error messages to the user and prevent the subsequent script termination. The On Error Resume Next statement accomplishes this. If you have used this statement in a procedure, then when an error is encountered in that procedure the program will continue without displaying an error message. The state of the error can be checked by using the err object's number, source, and description properties. These properties provide insight into the error code of the last error, the software component responsible for the error, and a text description of what that error was. The clear method can be used to clear the err object and set it back to its initial state as if no error had yet occurred. The raise method can be used to generate a run-time error to be caught by a higher level procedure or displayed to the user.
Tracing techniques can be of value in analyzing all types of errors. The easiest method is to simply insert a line of code that displays a message box after each statement and observe which message boxes are displayed. A more elegant approach is to use On Error Resume Next so that any run-time errors will not be fatal. Then, check the status of the err object after each critical call and only print out a message if that call caused an error. Yet another approach is to print out trace messages to a debug-specific form control such as an input text area control. Then you have what is essentially a window into the flow of the program.
If you're doing a lot of debugging, you'll want an even more powerful debug repertoire. You can obtain this by building your own debug routines. These routines can write trace information to a text area control, or even perform detailed variable analysis, displaying variable values and subtypes. A debug routine is easier to insert in your code than direct debug statements in some respects. This is because the debug logic is supplied just in one place, and the calls to the routine ensure consistency of debug approach throughout your program. Sample debug routines are provided in this lesson.
VBScript does not have a powerful set of debug tools like some languages do (such as VBScript's older cousin, Visual Basic 4.0). However, there is enough in the language to craft your own effective debug approach. The most important tools, however, are the same ones you supply for any language you must debug-your own persistence and patience.
Q | Suppose you have not used the On Error Resume Next statement in your source code, so that errors are addressed in the default manner. Is there any value to checking the err.number error code indicator at the end of each function to see if errors have occurred? |
A | No! Although you might think you are being a good, conscientious programmer by diligently checking error codes, if you didn't have an On Error Resume Next in your code, you'd be going through the motions for no reason. The On Error Resume Next statement tells the VBScript run-time interpreter to keep chugging along even if an error is encountered. If you haven't used this statement, then as soon as VBScript hits an error, it will generate a run-time error and bring your program to a screeching halt! The err.number check at the bottom of your function will never be reached. |
Q | Assume that you have used On Error Resume Next so that the program can continue if an error is encountered, and you can carry out error analysis at the end of your function. What is more helpful feedback to print out in a message box in your error analysis, err.Number or err.Description? |
A | Generally, err.Description is more helpful because it describes the cause of the problem. Of course, the description can sound rather vague at times (such as type conversion error), but that is more helpful than simply a numeric code. The numeric code can be helpful when users call in problems to a technical support person, because users generally can pass on a number correctly but
often paraphrase and inadvertently distort text messages. In this respect, printing out both err.Number and err.Description is often the best approach when considering technical
support needs. They are just two ways of describing the same problem: in terms of descriptive (or semi-descriptive) English, and in terms of a numeric code assigned by Microsoft.
The numeric code can come in very handy if you have to deal with the problem programmatically. If you need to write program logic to check if a certain type of error has occurred, it may be easiest to simply check err.number to see if the error code is the one you want to specially handle. |
Q | Can you use the err object to ignore certain errors and inform the users of others? |
A | Yes, if you couple it with On Error Resume Next! If you've used On Error Resume Next previously in the procedure, or at the top of your
script, VBScript will valiantly continue after encountering an error (after it updates the err object for you). Then, at some later point in your code, you can have code statements that inspect this value and take
action based upon it if it indicates the specific error you are concerned about. Assume, for example, that you don't want to tell your user about divide-by-zero errors because you'll just supply a default result in those cases; however, you want to inform
them of any other problem. The following code allows you to do just that:
if err.number = 11 then |
Q | Okay, so I decide I want to screen out any type-mismatch errors and let my users know about them, but don't want to tell them about other errors. How do I find out what the error code for the type-mismatch error is? |
A | The easiest way is to write a simple piece of test code. You actually want to put a bug in your code for once using this approach! It's actually kind of fun! Your test code should cause the error to
happen, and then you can print out the err.number value in a message box to figure out what the code is and apply it to other programs. Now you know what the error code will be when it occurs under real conditions! For
example, use this code to figure out what error number VBScript uses for a type mismatch:
On Error Resume Next count = 0 |
Trace the flow of an entire program by logging a trace message to a form textarea input control after each statement. You learned how to use this technique today. Use one of your own programs to trace, or use the Pace-Pal program used in this lesson.
Use a statement like the following to add the textarea input control that will be used as your trace window:
<FORM NAME="frmTestLog">
Debug Window: <TEXTAREA NAME="txtDebug" ROWS="8" COLS="60" ></TEXTAREA>
</FORM>
Then, after every normal program statement, add another statement right after it that will print a log message to the debug window you have defined. Use a variable declared to be the intrinsic Visual Basic constant vbCrLf to cause each log message to be generated on a new line. A sample declaration of this and other intrinsic constant-variable declarations can be found on the CD-ROM in shared\tools\constant.txt. The following code shows an example of using vbCrLf:
dim vbCrLf : vbCrLf = chr(13) & chr(10) ' Causes line break in text
document.frmTestLog.txtDebug.Value = _
document.frmTestLog.txtDebug.Value & _
"After statement such and such " & vbCrLF
Note |
Refer to Appendix C, "Answers to Quiz Questions," for the answers to these questions. |
r = q / p
Show the trace logic that you could add to help analyze the state of these variables and determine the problem.