Zero Defect Programming

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

If you got to this page via a Search Engine, click here to go to the start.
Send mail to Doug Anderson with questions or comments about this Web site.
Copyright © 1998-2007 Doug Anderson
Last modified: 20 Nov 2007