Using Visual Basic 6

Previous chapterNext chapterContents


- 21 -
Debugging Your Applications


Catching Undeclared Variables with Option Explicit

As you're coding, the Visual Basic IDE captures most statement syntax errors you make (see Figure 21.1). Syntax errors are considered spelling or keyword placement errors. These errors are normally easy to find and easy to fix.

FIGURE 21.1 The Visual Basic IDE catches If statements without the Then keyword as you type. However, it catches missing End If keywords only when you compile the code.

If you type like I do, you probably make spelling errors all the time. If you don't want VB to stop and display a message for each error, you can tell VB to highlight the line and allow you to keep typing. To change this option, choose Options from the Tools menu and clear the Auto Syntax Check check box (see Figure 21.2). Clearing this box doesn't prevent the compiler from finding errors when you attempt to run your program--it simply suppresses the normal syntax error messages that become highly annoying when you're typing lots of code.

FIGURE 21.2 Disable Auto Syntax Check if you don't want to see messages about your syntax errors while you're typing.

When you run your code within the IDE, Visual Basic reports errors such as type mismatches and incomplete code blocks (see Figure 21.3). Unless you have Option Explicit set, however, Visual Basic allows your code to run with undeclared variables. When you enter the keyword Option Explicit in a form or module's general section, all variables in your code must be declared explicitly by using one of the following keywords: Public, Private, Dim, or Static.

FIGURE 21.3 The IDE reports an incomplete Loop block when you compile the code.

Simple typing mistakes can lead to major errors in your code when you don't use Option Explicit. In Listing 21.1, you can see a typing error on line 6. The variable is declared with the name intMyNum, but line 6 attempts to use intMyNim instead. Because Option Explicit isn't declared, VB automatically creates a new variable called intMyNim and automatically initializes that variable to zero. Figure 21.4 shows the result. Had this code used Option Explicit, the IDE would have picked up the typing error when the code was run.

LISTING 21.1  A Bug Caused by a Typing Mistake

01 Private Sub cmdUnWit_Click()
02 Dim intMyNum As Integer
03
04 intMyNum = 2 + 2
05
06 MsgBox CStr(intMyNim)
07 End Sub

FIGURE 21.4 If you had used Option Explicit, you would have received an error message indicating that intMyNim was not defined.

Checking Code Segments with Breakpoints

You can easily stop your Visual Basic code at any point in its execution and examine it with breakpoints. A breakpoint is a place in your code at which you stop (break) your code during execution. You can set a breakpoint in four ways:


Clearing breakpoints

To clear all the breakpoints in your code, choose Clear All Breakpoints from the Debug menu. You can also clear all breakpoints by pressing Ctrl+Shift+F9.


When you set a breakpoint, notice that the line on which the break is set turns red. For your code to break, run the code. When the code breaks, the line on which you set the break turns yellow. Also, an arrow in the left margin of the Code window points to the line of code where execution has halted (see Figure 21.5).

FIGURE 21.5 To go to the next breakpoint, press the F5 key.

An arrow indicates the currently executing line

The present breakpoint line

A stop sign points out a break

Not all bugs are caused by errors in code syntax; most bugs are caused by an error in code logic or faulty design. These types of bugs are hard to find. You use breakpoints to narrow down the area of code where you think a bug is occurring. After you determine a line of code in which you know your bug occurs, you set a breakpoint and look at the erroneous area by using watches.

Monitoring Variable Values with Watches

Look again at Listing 21.1 and Figure 21.3. This code has a bug, and the reason is obvious--a typo. For educational purposes, however, suppose that you have no idea why the bug occurred. All you know is that the message box is reporting the wrong answer. This is a good opportunity to use breakpoints and watches.

Set a watch to inspect the value of the displayed variable

1. Set a breakpoint at the line of code that shows the message box.

2. Start your program.

3. Drag the mouse pointer over the variable whose value you want to view and let it stay there for a moment. A small window with the value of the variable appears.

When you watch the variables in question, you can see a discrepancy in values. Thus, you find that the code did assign the value properly to the variable intMyNum and that the second occurrence of the variable somehow lost the assigned value. Therefore, the addition logic is sound. The next step is to compare the spelling of the variables, which will lead you to the spelling mistake and the creation of the unwanted variable.

Monitoring Additional Variables with Add Watch

At times you need to watch more than one variable. To do so, you use the Watch window.

Add variables to the Watch window

1. Set a breakpoint or two to the variables you want to add. Press F5.

2. At each breakpoint, highlight the variable that you want to add.

3. Right-click and choose Add Watch from the pop-up menu.

4. Make the proper settings in the Add Watch dialog (see Figure 21.6) and then click OK.

5. To display the Watches window (see Figure 21.7), choose Watch Window from the View menu. This window also appears when you add a watch.

FIGURE 21.6 The Add Watch dialog box is a flexible, powerful tool.

FIGURE 21.7 In Break mode, make sure that you are at a place in your code where the variables listed in the Watch window are in scope.

Keeping track of many interdependent variables that change continuously can be very difficult. The Watch window is an effective tool for inspecting values in these dynamic situations, particularly in loops or arrays.

Examining Code Line by Line with Step Into and Step Over

You can step through your code to examine every line of code as it executes and in the order that it executes. You can step in two ways: step into and step over.

When you step into code, you move through your code line by line. If one line of code happens to call another procedure--an event procedure or one that's user defined--you step into that procedure (see Figure 21.8).

FIGURE 21.8 You can step into code by pressing F8 or by choosing Step Into from the Debug menu.

If you start your code with a step into, you might be surprised sometimes to see nothing happen. This is perfectly logical. If you have no code in the Form_Load() event procedure, there's no event to execute. Remember, Windows is an event-driven operating system; some event must fire for code to execute. The only real default startup code you have is a Form_Initialize(), Form_Load(), or Sub Main() procedure. If there's no code behind these events, your program will sit stagnant until an event is fired.

To step into some code, it's better to set a breakpoint where you want to begin to step and then run your code as you normally would. When you come to the breakpoint, invoke the step into feature to proceed. If you've used step into to enter a procedure and want to quickly exit that procedure, you can step out of the procedure by pressing Ctrl+Shift+F8 or by choosing Step Out from the Debug menu.

When you step over code, you also move through your code line by line. When you encounter a code line that calls another procedure, however, you don't enter the called procedure--instead, you execute that code as if it were solely a single line of code (see Figure 21.9).

FIGURE 21.9 If you don't want to debug the internal code of an event procedure, such as cmdUnWit_ Click(), step over it by pressing Shift+F8.

You might find it faster and easier to perform these debugging techniques by using the Debug toolbar (see Figure 21.10).

FIGURE 21.10 The Debug toolbar can be a floating window, or you can drag it to the toolbar area to dock it.

Stopping at Selected Lines with Run to Cursor

Every time you set a breakpoint, it stays in force until you return to your code and clear it or until you clear all the breakpoints by choosing Clear All Breakpoints (Ctrl+Shift+F9) from the Debug menu. Too many breakpoints make debugging slow and bothersome. You can make things easier by using Run to Cursor to stop your code at arbitrary points.

You click the code line on which you want to halt execution and then press Ctrl+F8 or choose Run to Cursor from the Debug menu. Then press F5 to run your code. The Visual Basic IDE stops execution of your code at the line that you clicked.

Using Advanced Debugging Tools

In addition to watching your code and moving through it by using the various step techniques, you can use the tools shown in Figures 21.11 through 21.14. Visual Basic provides these tools to do advanced debugging.

The Locals window (see Figure 21.11) is an easy way to view all the variables now in scope. Simple variables are listed with their values. Objects have a plus sign next to them, which you can click to view an object's properties. If a property is actually an object, you'll see another plus sign. In this window, you can easily see all variables at the same time without having to click each one.

FIGURE 21.11 The Locals window shows all the variables now in scope and their values. You access this window by choosing Locals Window from the View menu.

The Immediate window (see Figure 21.12) can be used to test lines of code without having to run your program. To try it out, enter the following in the Immediate window:

Print 2 * 3

Below the Print statement will be the answer 6.

The Immediate window works fine for one-line statements. You can't declare new variables in the window, but you can use any variable that's now in scope. For instance, if you stopped your program in a subroutine that had an intCounter variable defined, you could put this line of code in the Immediate window to see its value:

Print intCounter

You can also modify values in the Immediate window and execute methods on objects. Anything that requires a single line of code can be run here.

FIGURE 21.12 The Immediate window enables you to type in code and run it by pressing Enter.

The Call Stack dialog is useful if you're using many procedures and events. This dialog (see Figure 21.13) shows all currently active procedures, functions, and event handlers. The item listed at the top of the dialog is the current procedure, the one below it is the one that called it, and so on. This dialog is opened by clicking the Call Stack button on the Debug toolbar or by pressing Ctrl+L.

FIGURE 21.13 The Call Stack dialog shows you all the active procedures. This is functional only in Break mode.

To show the Quick Watch dialog (shown in Figure 21.14), put a break in your code, click a variable, or highlight an expression and choose Q_uick Watch from the Debug menu .

FIGURE 21.14 You can also invoke the Quick Watch dialog by pressing Shift+F9.

Using Find and Replace

If you've used any type of word processor, at some point you've probably done a Find and Replace. Find and Replace is a technique by which you search a document or portion of a document for a collection of characters and substitute as marked that collection with another--find all occurrences of Bob and change them to Joe, for example.

Find and Replace is a useful debugging tool that lets you make changes over a large expanse of code with relative ease. For the most part, the code you've seen in this book doesn't have a large line count, so finding and fixing something such as the misspelled variable encountered earlier isn't a difficult task. However, if you have a piece of code that runs more than 10,000 lines (which, by the way, isn't unusual for production code), the amount of work that you would have to do throughout the code to make a change--x + 2y to x * 2y, for example--would be considerable. Find and Replace makes things a bit easier.

Find and Replace works much as it does in a word processor. You select a set of characters that you want to locate and then invoke the process.

Find a set of characters

1. Choose Find from the Edit menu (Ctrl+F) to display the Find dialog.

2. Enter the word or characters you want to find in the Find What combo box.

3. Click the Find Next button (see Figure 21.15).

4. To display the Replace dialog, click the Replace button (see Figure 21.16).

FIGURE 21.15 The Find dialog has many logical search features built in.

FIGURE 21.16 You can access the Replace dialog by choosing Replace from the Edit menu or by pressing Ctrl+H.

Designing Applications for Debugging

If you're planning to do much programming, chances are you'll end up writing some large programs that have many changes in them. The problem with debugging by using breakpoints and watch windows is that they aren't permanent. For instance, if you know about a problematic piece of code that causes errors each time you modify it, wouldn't it be nice to permanently embed some debugging code nearby so that you could quickly diagnose your error? You can make this work in Visual Basic in a number of ways, the easiest and most efficient being conditional compilation. Conditional compilation is the process through which VB can exclude pieces of code if certain conditions aren't met. C/C++ programmers have had this for years with #ifdef and related statements. VB made this feature available in a previous version of the product, but it hasn't been widely used yet.

This section shows you how to use conditional compilation to embed permanent debugging code in your application. The debugging code will never actually make it to the client, however--VB will leave it out.

Listing 21.2 is a simple piece of code that sums the numbers from 1 to 20 and displays the result.

LISTING 21.2  Simple Addition Code

01 Private Sub Form_Load()
02 Dim intCounter As Integer
03 Dim intSum As Integer
04 intSum = 0
05
06 For intCounter = 1 To 20
07 intSum = intSum + intCounter
08 Next intCounter
09 MsgBox "Sum is " & intSum & "."
10
11 End Sub

Suppose that you're unsure whether the addition is working right. An easy way to test it is to add a Debug.Print statement, as shown on line 8 of Listing 21.3.

LISTING 21.3  Simple Addition Code with Output

01 Private Sub Form_Load()
02 Dim intCounter As Integer
03 Dim intSum As Integer
04 intSum = 0
05
06 For intCounter = 1 To 20
07 intSum = intSum + intCounter
08 Debug.Print "Value: " & i & ", New Sum: " & intSum
09 Next intCounter
10 MsgBox "Sum is " & intSum & "."
11
12 End Sub

The results of the Debug.Print statement will show up in the Immediate window when this program runs in the VB environment. Whenever you compile this program for distribution, however, those Debug.Print statements won't do anything because there's no Immediate window to print to. For this reason, it makes sense to leave them out by using conditional compilation. To do this, the Debug.Print statement must be wrapped with conditional operators, as shown on lines 9-11 in Listing 21.4.

LISTING 21.4  Output Wrapped in Conditional Compilation Tags

01 Private Sub Form_Load()
02 Dim intCounter As Integer
03 Dim intSum As Integer
04 intSum = 0
05
06 For intCounter = 1 To 20
07 intSum = intSum + intCounter
08
09 #If DEBUG_ON Then
10 Debug.Print "Value: " & intCounter & ", New Sum: " _
& intSum
11 #End If
12
13 Next intCounter
14 MsgBox "Sum is " & intSum & "."
15
16 End Sub


Why use conditional compilation?

The best part about the conditional compilation code is that all the conditional parts are removed before the executable is built. If a condition was True when the code was compiled, the code within the If...Then is left in the resulting VB code. If the condition was False, the wrapped code is completely removed. This way, your code can be smaller and faster, especially because the processor doesn't have to do any work at runtime to evaluate debug flags.


The code between #If and #End If is evaluated only if the #If condition is True. If not, the code essentially doesn't exist in the VB environment. You can't do any normal debugging tasks on code wrapped in this manner when the condition isn't met. For the code to work, you have to add the following line to the form's declarations section:

#Const DEBUG_ON = True

This special definition causes the DEBUG_ON compilation constant to be True. The Debug statement will now print out properly.

Creating an Error Handler

If you've ever used a software package and had it crash on you, you were the victim of one of the biggest problems in programming: error handling. Every program needs to be responsible for itself and any errors that can occur. In this section, you see how to use VB's built-in error-handling capabilities. You also will build a simple error handler that can be expanded for your own applications.

See what happens when you don't handle errors

1. Start a new project.

2. In the Form_Load event handler, add this code:

Dim intTest as Integer
intTest = 100 / 0


3. Run your project.

Because division by zero is an error, VB stops and generates an error message. If your program had been running as an executable, users would receive the same error message you did, and the program would immediately exit. This isn't good programming style.

Luckily, you have a few ways to deal with runtime errors through Visual Basic's error handling. The first of these is the On Error Resume Next command. This statement, used as part of an error handler, causes Visual Basic to skip the error and go on. This type of error handler is useful when you don't necessarily need to resolve the error. For instance, if an error occurs while you're exiting the program, there isn't any point in handling the error, because you're exiting anyway.

To try out On Error Resume Next, replace your Form_Load code with the following code:

Dim intTest As Integer
On Error Resume Next
intTest = 100 / 0
Debug.Print "Program is past the error."

Because you now have an error handler, VB can detect the error and skip the line that the error occurs in. In the Immediate window, you will see the results of the Print statement you created.

In this case, the error handler will save your program from crashing. However, not solving this error here can cause other errors later in the program. Farther on, some code that needs the value of the intTest variable might not work, and you will end up with an even bigger error, known as a cascading error.

A cascading error is much like multiple-car pileups that occur on interstate freeways: The first car crashes into something, the next car can't stop and runs into the first, and so on, until you have a real mess.


Looking at all the errors

You can find other trappable errors for VB by searching for Trappable Errors in your VB help file. Literally hundreds of errors can happen. Also, each control or library that you add can have its own errors defined. Refer to each library/control's documentation for information about errors that can occur.


To help prevent these types of nightmares, Visual Basic provides additional error-handling features, such as the capability to create a more specific error handler. For instance, you want to let users reattempt to open their CD-ROM drive but don't want to continue if a division-by-zero error occurs. To do this, replace your Form_Load code with the following (minus the line numbers, of course):

01 Private Sub Form_Load()
02   Dim intTest As Integer
03   Dim intRet As Integer
04   On Error GoTo EH
05   intTest = 5 / 0
06   Exit Sub
07
08 EH:
09   If Err.Number = 11 Then
10      MsgBox "Division by zero error occurred.", _
           vbCritical
11      End
12   ElseIf Err.Number = 71 Then
13      intRet = MsgBox("That drive is not ready.", _
           vbExclamation + vbAbortRetryIgnore)
14      Select Case intRet
15      Case vbRetry
16         Resume
17      Case vbAbort 
18         End
19      Case vbIgnore
20         Resume Next
21      End Select
22   `
23   ` more conditions follow
24   `
25   End If
26 End Sub

First, the On Error statement now uses the GoTo statement (line 4) to tell VB where to go in case of an error. In this case, you want to go to a block of code labeled with EH (line 8). Labels are used almost exclusively for error handling and are simply text followed by a colon, like the EH: just above the error-handling code.

This error-handling section (lines 9-25) checks the Number property of the Err object. This number determines what the error is. This particular block checks for a division-by-zero error, which obviously will occur. When it does, the programmer has decided that it is serious enough to require the program to shut down. The End statement (line 18) will immediately shut down the program.

The next error to check for is the Drive Not Ready error, which happens frequently when you try to access a CD-ROM or floppy drive without a disk in it. This particular case offers users three choices:

You can see the code for each value of the MsgBox function in the preceding example. After the Drive Not Found error, you typically would have more error-handling blocks to handle other types of errors that might occur during the operation of your program. You can use Select Case to handle groups of errors in a similar way, because the errors are numbered. For instance, all the Drive Not Ready error types should be handled in the way used here--that is, allow users to abort, retry, or ignore. Other error types, such as division by zero and other critical errors, will normally cause the program to fail--maybe not immediately but possibly later.

The most important thing to remember about error handling is that every procedure with significant code in it needs an error handler. You can name all the error-handling blocks the same--that is, they can all be labeled EH. This makes it easier for you to duplicate code and drop in as you need it.


Previous chapterNext chapterContents

© Copyright, Macmillan Computer Publishing. All rights reserved.