Chapter 17
Exterminating Bugs from Your
Script
CONTENTS
If you have been a programmer for very long, you probably realize
that writing programs is sometimes the easy part of software development.
It's getting the bugs out that can be the real challenge! Judgment
is needed to effectively sift through many hundreds or thousands
of possible causes of a problem and hone in on a specific bug.
At the same time, 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 textguide 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.asp on the
CD-ROM. Like the other programs you've learned about in this guide,
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.asp. The CD-ROM also contains a main viewer page, default.asp, 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.asp.
|
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:
- Number-The numeric error code. This value is set by the VBScript
interpreter when an error occurs. 0
represents no error, and any other number means an error has occurred.
Each type of error has its own specific error code. Type conversion
errors, for example, always generate an error code of 13.
Error codes can range from 1
to 65535 for VBScript.
- Description-A description of the error that corresponds to
the error number. When err.number
contains 13, err.description
will contain the corresponding Type conversion
text description. Note that a description doesn't necessarily
exist for every number.
- Source-Who caused the error. This would be, for example, the
name of your VBScript project if the error was caused within VBScript,
or the name of an OLE automation object if it was caused by an
OLE automation component.
- Helpfile-The path and filename of a help file, if relevant,
containing more details on the error.
- HelpContext-A help file context ID (topic index) that corresponds
to the help file error information. The help file and context
information can be used to make your program open a relevant help
file containing further information on the error.
There are two methods for using the err
object, which are explained more fully in the next section:
- Raise-Generates an error.
- Clear-Resets the contents of the error object.
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.asp.
|
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.
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.
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.asp.
|
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.asp.
|
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.
Figure 17.11 : Output from the first message box trace after the error, with trace on decomposed statements.
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.asp
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=3>
<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=3> _ Pace
per Kilo: <INPUT NAME="txtPaceKilos" VALUE=""
MAXLENGTH="5" SIZE=3>
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.asp.
|
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:
- On Error Resume Next
err.description
err.source
err.number
err.clear
err.raise
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
' Divide by zero err code is 11, so supply default
' result in this case
ResultVar = 100
else if err.number <> 0
' Any other errors, let user know
msgbox "Warning, an error has occurred," & _
" please report it to tech support: " & _
err.Description, vbOKOnly, _
"Error Detected In Mortgage Calculator"
end if
'
.normal processing
.
|
| 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
Dim b
b = "dog" * "cat"
msgbox "I caused an error! And I'm not sorry! Err number is " _
& err.number & _
" and description is " & err.description, vbOKOnly, _
"Pinpointing an Error Code"
These error codes are, for the most part, the same as those used in Visual Basic 4.0 and VBA. At the time of this printing, no consolidated source of documentation seems to exist, but it is easy to generate this yourself for VBScript errors.
Simply write a program that loops through and prints every error:
count = 0
do while count <= 65535
' set the error code
count = count + 1
err.Number = count
' Print out the corresponding error message for this error code
txtErrorList = txtErrorList & err.Description & vbCrLf
loop
|
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.
|
- Take a look at the following conditional expression. Assume
that you want to trace its logic. Insert the necessary trace statements
to fully understand the run-time logic flow of the conditional.
(There are many different ways to achieve this, so there is not
just one correct answer!) Here's the code:
If a > 3 then
b = c + 1
else
if a = 1 then
b = c + 4
else
if < -2 then
b = c + 7
end if
end if
end if
- Assume that you've just had the sobering experience of seeing
a Type mismatch error pop
up in front of you. After some intense words with your computer
and some focused troubleshooting, you isolate the problem to this
statement:
r = q / p
Show the trace logic that you could add to help analyze
the state of these variables and determine the problem.
  
|