|
Visual Basic Code Examples
This section is a bit long, because of the Visual Basic code.
Here are the links to the sections within it:
The Practicalities: explains the debugging
concept, with code for a non-hierarchical debugging trace.
The Bells and Whistles: explains how to
improve the usefulness of the debugging trace.
adding a hierarchical
display;
adding optional
file and printer output;
allowing for multiple instances of your program.
The
Practicalities
Using Visual Basic as an example, here is a simple technique
to ensure your code is bug-free and 100% reliable. If you are
not familiar with VB, note that variables that end with a "$"
are character strings; those that end with "%" are
16-bit integers. Comments begin with a single quote mark and
are shown in green.
1. First define a global Boolean variable (that is, a flag
or switch) that will indicate if we are, or are not, in
debug mode.
Global WantDebug
as Boolean 'TRUE
means debugging is invoked
2. At the beginning of the program, before the very first
executable line, add two statements. The first turns the debugging
switch on or off, the second displays a positive statement that
debugging has started, but only if the switch is on.
WantDebug = True 'turn on debugging
If WantDebug then DebugPrint "General
Ledger debugging started at " & Time$
The ampersand (&) in Visual Basic means "concatenate"
or "join" the two strings on either side. Time$
is a function that returns a string containing the current date
and time in a readable format. The DebugPrint is a call
to a subroutine, which is passed the string immediately following.
3. Add a subroutine, called DebugPrint, that is responsible
for printing a string passed to it. The reason we use a subroutine,
rather than the built-in Visual Basic Debug.Print statement
is that we will have various versions of this routine (described
later) that will optionally allow making a debugging trace file,
a printout, etc. For now, simply code the routine as follows:
Sub DebugPrint(msg$)
If Right$(msg$,1)
= ";" then
Debug.Print
left$(msg$,len(msg$)-1);
Else
Debug.Print
msg$
End If
End Sub
The output from this routine will be displayed in the VB debugging
window.
This subroutine simply checks the last byte of the string
passed to it. If the last byte is a semi-colon, then the string
is printed (minus the semi-colon) without a carriage return at
the end; that is, subsequent strings will print on the same line,
but the trailing semi-colon is never printed. If the last byte
of the string is not a semi-colon, the string is printed in its
entirety, followed by a carriage return.
4. Now add two statements to EVERY subroutine, including any
event routines that you use belonging to items on the screen
forms. If you do this as you write the program, this is not a
chore. If you try to add these to an existing program, then it
can be a huge task; you can simplify this by using a keyboard
macro utility, such as KeyText 2000 available from MJMSoft Design
.
Put this statement as the first executable line in the subroutine:
If WantDebug then
DebugPrint "[subroutinename]"
If the subroutine has arguments passed to it, add them so they
are displayed and you know what the routine actually gets. For
example,
If WantDebug then
DebugPrint "[subroutinename] Account=" & cstr$(Account)
The cstr$ function in VB converts a numeric value to a
string.
Put this statement as the last executable statement:
If WantDebug then
DebugPrint "Exiting [subroutinename]"
If the subroutine has multiple exit points, add GoTos
and jump to the last DebugPrint statement, so it always gets
executed. It is often helpful to display result values on this
final DebugPrint statement, as well.
5. Now look at the code in the subroutine. Assume the routine
will fail. What variables must you display as the routine
executes to find out what is wrong? Display them with the same
syntax:
If WantDebug then
DebugPrint "Whatever="
& cstr$(Whatever) & ";"
DebugPrint ";
Something=" & cstr$(Something)
End If
Note that these will be printed on the same line, because the
first line ends with a semi-colon.
6. Run the program. If you've been thorough, you will get
a stream of debugging messages as you click on things in the
program's windows, and you will have a trace of events as they
occurred and what the program did about them.
7. If you find that some of the loops in your program generate
massive amounts of output, here are a few ways to reduce the
output:
- set up a counter, and print a line for every 50th (or 100th
or 500th) iteration instead of every time through the loop;
- set up a second boolean debug switch, which is normally off;
test this switch as well as (or instead of) the normal debugging
switch;
- only print exceptional conditions, instead of the common
conditions;
- print a summary at the end of the loop, instead of on every
iteration (but what happens if the program crashes in the middle
of the loop)?

The
Bells and Whistles
We can improve this debugging trace listing in several ways:
- First, we can make it hierarchical, so that we can see the
nesting of subroutines. This simple change vastly improves the
usability of the debugging trace.
- Secondly, we can write it to a file, or to a printer, or
both, so that a remote user could e-mail or fax you the debugging
trace.
- Thirdly, if multiple instances of your program could be running
simultaneously, we can have the program generate a trace file
that has an incrementing file number, to avoid having two instances
writing to the same file simultaneously. This last technique
is extremely useful for ActiveX objects and DLLs, because they
often exist in multiple instances within a project.
Here
is another version of DebugPrint that has a hierarchical display;
it relies on the fact that every routine starts with "[xxx]" and ends with "Exiting [xxx]". (If you haven't
been consistent in this, the display will not be hierarchical.)
Each time a subroutine starts, the debugging display shifts two
spaces to the right; each time a subroutine ends, the display
shifts two spaces to the left.
First, put these definitions in your program:
Global dbgIndentLevel As Integer
'indicates indentation level
Global dbgSameLine As Boolean 'print on same line?
Sub DebugPrint(Msg$)
Dim i as Integer, temp as String
If dbgSameLine
Then
temp$ = Msg$
Else
temp$ = ""
If Left$(Msg$,
1) = "[" Then
dbgIndentLevel
= dbgIndentLevel + 1
End If
For i = 2 To
dbgIndentLevel
temp$
= temp$ & " " '2 spaces for indentation
Next
temp$ = temp$
& Msg$
End If
If Right$(Msg$, 1) = ";" Then
Debug.Print Left$(temp$,
Len(temp$) - 1);
dbgSameLine =
True
Else
Debug.Print temp$
dbgSameLine =
False
End If
If Left$(Msg$, 4) = "Exit"
Then
dbgIndentLevel
= dbgIndentLevel - 1
If dbgIndentLevel
< 1 Then dbgIndentLevel = 0
End If
End Sub
The
next version of DebugPrint adds optional file and printer
output to the hierarchical display:
Global dbgIndentLevel As Integer
'indicates indentation level
Global dbgSameLine As Boolean 'print on same line?
Global TraceFile As Integer 'text debugging messages
Global TraceFilename As String 'name of our trace file
Global WantPrinterTrace as boolean 'TRUE
if printer output
Global WantTraceFile as boolean 'TRUE if file output
The variable TraceFile is set to 0 if there is no output
file, and non-zero otherwise. When the program starts, set the
variable TraceFilename to have the filename of the debugging
file:
TraceFilename =
"C:\MyTrace.txt"
Also set the debugging option switches:
WantPrinterTrace
= False
WantTraceFile = True
Sub DebugPrint(Msg$)
Dim i as Integer, temp as String
If TraceFile = 0 and WantTraceFile Then
If TraceFilename
<> "" Then
TraceFile
= FreeFile 'allocate
a new file number
Open
TraceFilename For Output As #TraceFile
Print
#TraceFile, ">>> Print this file with WordPad in
landscape mode <<<"
Print
#TraceFile, " "
End If
End If
If dbgSameLine Then
temp$ = Msg$
Else
temp$ = ""
If Left$(Msg$,
1) = "[" Then
dbgIndentLevel
= dbgIndentLevel + 1
End If
For i = 2 To
dbgIndentLevel
temp$
= temp$ & " " '2 spaces for indentation
Next
temp$ = temp$
& Msg$
End If
If Right$(Msg$, 1) = ";" Then
Debug.Print Left$(temp$,
Len(temp$) - 1);
If TraceFile
<> 0 Then Print #TraceFile, Left$(temp$, Len(temp$) - 1);
If WantPrinterTrace
then Printer.Print Left$(temp$, Len(temp$) - 1);
dbgSameLine =
True
Else
Debug.Print temp$
If TraceFile
<> 0 Then Print #TraceFile, temp$
If WantPrinterTrace
then Printer.Print temp$
dbgSameLine =
False
End If
If Left$(Msg$, 4) = "Exit"
Then
dbgIndentLevel
= dbgIndentLevel - 1
If dbgIndentLevel
< 1 Then dbgIndentLevel = 0
End If
End Sub
The
final version of DebugPrint allows multiple instances;
the trace file has an incrementing number in the filename.
Global dbgIndentLevel As Integer
'indicates indentation level
Global dbgSameLine As Boolean 'print on same line?
Global TraceFile As Integer 'text debugging messages
Global TraceFilenameRoot As String 'name
of our trace file
When the program starts, set the variable TraceFilenameRoot
to have the first part of the trace filename:
TraceFilenameRoot
= "C:\MyTrace"
This version of the subroutine checks to see if a file with
the name "C:\MyTrace.001.txt" already exists; if so,
it tries "C:\MyTrace.002.txt", and so on. When it finds
one that does not exist, then it creates that filename and uses
that as the output trace file.
Sub DebugPrint(Msg$)
Dim i as Integer, temp as String, Filename
as String
If TraceFile = 0 Then
If TraceFilenameRoot
<> "" Then
Filename$
= TraceFilenameRoot
temp$
= Filename$ & ".001.txt"
If
FileFind(temp$) Then
For
i = 2 To 999
temp$
= Filename$ & "." & Format$(i, "000")
& ".txt"
If
Not FileFind(temp$) Then Exit For
Next
End
If
TraceFile
= FreeFile
Open
temp$ For Output As #TraceFile
Print
#TraceFile, ">>> Print this file with WordPad in
landscape mode <<<"
Print
#TraceFile, " "
End If
temp$ = ""
End If
If dbgSameLine Then
temp$ = Msg$
Else
temp$ = ""
If Left$(Msg$,
1) = "[" Then
dbgIndentLevel
= dbgIndentLevel + 1
End If
For i = 2 To
dbgIndentLevel
temp$
= temp$ & " " '2 spaces for indentation
Next
temp$ = temp$
& Msg$
End If
If Right$(Msg$, 1) = ";" Then
Debug.Print Left$(temp$,
Len(temp$) - 1);
If TraceFile
<> 0 Then Print #TraceFile, Left$(temp$, Len(temp$) - 1);
dbgSameLine =
True
Else
Debug.Print temp$
If TraceFile
<> 0 Then Print #TraceFile, temp$
dbgSameLine =
False
End If
If Left$(Msg$, 4) = "Exit"
Then
dbgIndentLevel
= dbgIndentLevel - 1
If dbgIndentLevel
< 1 Then dbgIndentLevel = 0
End If
End Sub
The function FileFind checks to see if a file already
exists, and returns True if so:
Function FileFind(what$) as Boolean
Dim temp$
temp$ = ""
On Error Resume Next
temp$ = Dir$(what$)
On Error GoTo 0
If temp$ = "" Then
FileFind = False
Else
FileFind = True
End If
End Function |