Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Schongar P.VBScript unleashed.1997

.pdf
Скачиваний:
45
Добавлен:
23.08.2013
Размер:
1.59 Mб
Скачать

vHours = Left(sDuration, iPosition - 1)

' Parse string for further processing

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

' Get middle time component

iPosition = InStr(sDuration, ":")

If iPosition = 0 Then

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

vMinutes

= vHours

vSeconds

= sDuration

vHours =

0

Else ' Time

info must be in hh:mm:ss format

vMinutes

= Left(sDuration, iPosition - 1)

vSeconds

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

End If

 

End If

'Represent all components in terms of seconds

vHours = vHours * 3600

vMinutes = vMinutes * 60

'Return total seconds value

ConvertStringtoTotalSeconds = CInt(vHours) + _

CInt(vMinutes) + CInt(vSeconds)

End Function 'ConvertStringtoTotalSeconds

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

the error.

Using the MsgBox Statement

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

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

NOTE

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

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

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

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

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

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

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

Sub Test_OnClick

Dim a, b, c

a = text1.text * 3

c = text2.text * 4

d = a + c

End Sub

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

Sub Test_OnClick

Dim a, b, c

MsgBox "Point 1"

' We've made it to point 1

a = text1.text * 3

 

MsgBox "Point 2"

' We've made it to point 2

c = text2.text * 4

 

MsgBox "Point 3"

' We've made it to point 3

d = a + c

 

MsgBox "Point 4"

' We've made it to point 4

End sub

 

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

Using the OnError ResumeNext Statement

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

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

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

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

Function ConvertStringToTotalSeconds (ByVal sDuration)

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

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

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

'When error occurs, continue with next statement

'rather than halting program

On Error Resume Next

Dim iPosition

'Position of ":" seperator

Dim vHours

' Number of hours required

Dim vMinutes

' Number of minutes

required

Dim vSeconds

' Number of seconds

required

NOTE

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

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

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

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

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

Using the Err Object

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

NOTE

For an in-depth treatment of the Err object, refer to Chapter 10, "Error

Handling." In this chapter, the Err object is considered specifically with respect to debugging your VBScript applications.

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

Number: The numerical error code. This value is set by the VBScript Interpreter when an error occurs. 0 represents no error, and any other number means that an error has occurred. Each type of error has its own specific error code. Typeconversion errors, for example, always generate an error code of 12. 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 12, Err.Description contains the corresponding Type conversion text description. Note that a description doesn't necessarily exist for every number.

Source: Tells what caused the error. This can be the VBScript page if the error was caused within VBScript, for example, 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 were detected. Any other value represents an error code. If you do find an error code, the Err.Description field provides the standard text description of the error. This message is the same one that pops up on the runtime message error box when the program screeches to a halt if you aren't using On Error Resume Next to ignore the errors. You can even look at the originator of the error in the Source property. Most often, the source is the name of your VBScript file itself if it was your VBScript code that caused the problem. In some cases, however, you might find that the error source is a component you have integrated, such as an ActiveX control. You can even get information on associated Help files for errors, although this information is less likely to be of use to you in structuring your error-recovery code. The code sequence in Listing 12.3 shows one example of how you might check to see whether an error has occurred.

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

Sub Test_OnClick

On Error Resume Next

Dim a

a = text1.text / text2.text

If Err.Number <> 0 Then

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

End If

End Sub

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

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

vMinutes = Left(sDuration, iPosition - 1)

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

End If

End If

'Represent all components in terms of seconds

vHours = vHours * 3600

vMinutes = vMinutes * 60

'Return total seconds value

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

If Err.Number <> 0 Then

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

Description _

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

ConvertStringtoTotalSeconds!"

End If

End Function ' ConvertStringtoTotalSeconds

NOTE

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

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

Figure 12.5 : Information about the error.

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

these error-handling mechanisms. Next, you'll learn a few more advanced details of the Err object and the On Error Resume Next statement, and then look at more tracing techniques. You'll then return to the quest for your Pace-Pal bug.

Taking the Err Object Further

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

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

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

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

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

Err.Raise 13

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

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

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

flows to the first higher level calling procedure that has an active error handler.

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

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

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

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

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

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

Using Advanced Debugging Techniques

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

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

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

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

Tracing Your Code Using the Message Box

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

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

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

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

vMinutes

= vHours

vSeconds

= sDuration

vHours =

0

Else ' Time

info must be in hh:mm:ss format

vMinutes = Left(sDuration, iPosition - 1)

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

End If

End If

' Represent all components in terms of seconds

vHours = vHours * 3600

vMinutes = vMinutes * 60