An Assertion Implementation
Here’s one way to code an assertion procedure in Visual Basic:
Sub BugAssert(ByVal fExpression As Boolean, _
Optional sExpression As String = sEmpty)
#If afDebug Then
If fExpression Then Exit Sub
BugMessage “BugAssert failed: “ & sExpression
Stop
#End If
End Sub
This short assertion procedure packs in some interesting twists. First is the optional argument. The intention is to fill the argument with a string containing the same information you would get from the C compiler. You could fill in both arguments yourself when writing code, but normally you would just use the simple version at design time:
BugAssert 2 + 2 = 5
Later you use a text-processing program that scans through source code and automatically fills in the second argument:
BugAssert 2 + 2 = 5, “2 + 2 = 5, file LIES.BAS, line 325”
Visual Basic might not know the expression, file, and line, but it’s easy enough to write a program that does. Microsoft refers to programs that modify source code as wizards, and Visual Basic includes several of them. The Debug Wizard (BUGWIZ.EXE) shown in Figure 1-2 is the first of several wizards used in this book. For now, we’re interested only in the program; we’ll return to the code in Chapter 3.
The line number displayed in asserts was of limited use in previous versions
of Visual Basic, but finally, in version 5, you can actually see a status display that tells what line and column the cursor is on. Unlike all the other programmer’s editors I’ve seen, the Visual Basic editor doesn’t have a Goto command to move to a specified line. But first things first. At least we get the current line displayed on the toolbar (even though it took five versions).
The next interesting thing about the code is the Exit Sub statement in the first unconditional line. For readability of structured code, I would normally use an If/End If block around the rest of the code rather than an Exit Sub. In this case, however, the important thing is not readability but that the Stop statement be the last executable statement of the procedure. This makes it more convenient to step back through the assertion procedure and come out in the calling procedure ready to debug.
Figure 1-2. The Debug Wizard.
Speaking of the Stop statement, this is a wonder that C compilers and preprocessors can’t match. It puts you in a perfect position to step into the code that caused the bug and fix it on the spot, assuming that you’re working in the IDE. Of course it doesn’t help if your program is an EXE or DLL, but in that case you just want to terminate with a message anyway. BugMessage will display the message, and then Stop will terminate.
You might wonder why I call the procedure BugAssert instead of Assert. I put all my debug procedures in the DEBUG.BAS module and start each name with Bug. Then I can easily search for all words that start with Bug and replace them with ‘ Bug, thus commenting out all debug calls before I ship my program. Debug Wizard performs this operation automatically. (Incidentally, that’s another reason all my Bug routines are subs, not functions. You can’t comment out functions in expressions mechanically.)
You might also wonder why you can’t use conditional compilation to automatically comment out unnecessary code. Well, you can if you have a preprocessor that supports macros. In C, assert is a macro that, when debugging is on, converts the statement
assert(2 + 2 == 5)
to a statement roughly equivalent to this:
if !(2 + 2 == 5)
printf(“Assertion failed: %s, file %s, line %d\n”,
“2 + 2 == 5”, “lies.c”, “6”);
Conditional compilation is a familiar feature in many languages, but it might be new to Basic programmers (although old-timers might remember metacommands from MS-DOS and Macintosh Basic compilers). Basic conditional statements (like their metacommand ancestors) give instructions to the compiler (or interpreter) about how to create the program. The results of the commands live on at run time, but the commands themselves are long gone.
Another way to think of this is that conditional compilation is an easy way of commenting out blocks of code. Let’s say you want to try out different destroy algorithms. In previous versions of Visual Basic (or perhaps using the Comment Block and Uncomment Block buttons on the Edit toolbar in this version), you might have written this:
SafeSlowDestroy earth
‘FastRiskyDestroy earth
After running the safe, slow version a few times to ensure that it works, you would have commented out the first line and removed the comment from the second to test it. Of course, in reality, each block would probably have more than one line, and affected code might be scattered in several places. Changing these calls by hand is a major inconvenience with great potential for bugs. Conditional compilation makes it easy to switch back and forth:
#Const fSafeSlow = 1
#If fSafeSlow Then
SafeSlowDestroy earth
#Else
FastRiskyDestroy earth
#End If
To try the fast, risky version, simply change the definition of the fSafeSlow
constant to 0. Notice that the syntax for compile-time tests and constants is exactly the same as for run-time tests and constants except that the compile-time lines start with a pound sign (#). If you don’t define an fSafeSlow constant in this example, it is assumed to be 0. This default enables you to fake the C language #ifdef and #ifndef statements even though Visual Basic doesn’t directly support them.
Conditional Compilation for Blockheads
In Visual Basic, there’s no relation or communication between compile-time and run-time statements. You can’t use a constant created with #Const in an If statement, and you can’t use a constant created with Const in an #If statement. Despite syntax similarities, compile-time Visual Basic and run-time Visual Basic are different languages handled by different language interpreters at different times.
If debugging is off, the preprocessor will produce a blank—absolutely no code. If you look back at BugAssert, you’ll see that if debugging is off, it will produce something—an empty procedure.
Imagine calling an empty procedure hundreds of times in the code you ship to customers. Imagine doing it in p-code. In native code, an empty procedure is just one instruction, but you still have all the code to evaluate the expression and push arguments onto the stack. An empty procedure might be comparatively cheap, but the motto of the defensive programmer is “When in doubt, assert.” Personally, I can’t follow this advice comfortably if I know that every assertion costs a call, a return, and many worthless bytes of code. I wrote Debug Wizard to eliminate all that code. I need to run it only occasionally during the life of a project.