Debugging in a Complex Loop
by Bob Rimmington, Independant Developer

Introduction

Tracing and correcting faults in a program can be done in many ways. Using the “Fix” option when an error is encountered, using the Debugger or just scrutinising the code very carefully probably covers most situations. But none of these may prove an ideal way to find an obscure bug buried somewhere inside a complex loop. Single stepping is possible but very tedious if a Do .... Until loop contains a hundred plus lines of code and only a few records out of several thousand are being processed incorrectly. In such circumstances it may prove far easier to use a few simple commands that have been in dBASE so long that it is all too easy to forget them in these days of visual tools and OOP.

The Method

Let us assume that you have a function or program that loops through every row in a table. It checks fields in each row against a value(s) entered by the user and possibly other criteria and then processes the data in some way. Such as change the data in a field or write rows to a new table. The details do not matter and it has seemed best to omit an example as explaining a suitable complex routine could make this appear far more complicated than it really is.

So let us assume that within your function you have a line saying  Do While ... or just Do. Then immediately before such a line insert:

SET TALK ON        &&&&&&
SET ALTERNATE TO TALK1.TXT  // or any file name you choose &&&&&&
SET ALTERNATE ON   &&&&&&

This will display all assignments to variables and fields and additionally write the output to the file specified. So that you know what is happening you may need to insert a few extra line that put a simple message into a variable. This will then also be written to the file. For example:

If a.first()
? 'Date Entered '+ dStart         &&&&&&&
  // Now at top of Rowset, start loop
  Do
? 'New Record '+a.Fields['ADAUDREF'].value    &&&&&&
    lFound:=False            // reset for each record
    Do Case
      Case cRepType = 'A'
? 'Stage01 '+ a.Fields['ADTRDATE'].value+' <= '+ dStart   &&&&&&&&
        If a.Fields['ADTRDATE'].value <= dStart
          lFound:=True
          ...................
          If lFound
? 'Stage02 - lFound'         &&&&&&&
            // Check if new Client and/or Invoice No
       ...................    
    Until a.Next()
Endif  // If a.first()
Note that to aid later deletion of all the temporary extra lines I do not inset them and include a &&&&&& with each (to help search for any I might miss!). At the end of the loop, you must of course reverse the initial three SET... lines.

Now for an example of what might be written to the file:

Date Entered 31/12/99
New Record 003743
false
Stage01 29/11/99 <= 31/12/99
true
Stage02 - lFound
Stage08 - ......
21/08/99
D
      -123.00
29/11/99
00175
T00008
Stage09 - Reset
00175
T00008
New Record 003014
false
Stage01 31/07/99 <= 31/12/99
true
Stage02 - lFound

OK, not very meaningful outside the context of an individual function but if it applies to your own code it is quite easy to check how each record in turn is being handled. Even if you have to scan down several hundred lines before you find the error, for me at least it is far quicker than single stepping.

Comparing the Talk

Obscure problems in such loops often surface when a user enters different values and then finds that the output, say to a report, does not differ in the way he would expect. Such as a list of all debtors over the value entered. The higher the value the shorter should be the list. If in fact a higher value gives a longer list then something is wrong! This in fact can often make tracing the error much easier. Just run the program two or three times entering a different value each time but first change the output file name for each run. What we then need to find is where the output lists differ. If you have an editor that allows columns of text to be blocked and then pasted, then that is one simple way. I use the DOS QEdit which does this perfectly:

 1st Run
 2nd Run
Date Entered 31/12/99
New Record 003743
false
Stage01 29/11/99 <= 31/12/99
true
Stage02 - lFound
Stage08 - ......
21/08/99
D
      -123.00
29/11/99
00175
T00008
Stage09 - Reset
00175
T00008
New Record 003014
false
Stage01 31/07/99 <= 31/12/99
true
Stage02 - lFound
Date Entered 31/12/99
New Record 003743
false
Stage01 29/11/99 <= 31/12/99
true
Stage02 - lFound
Stage08 - ......
21/08/99
D
      -123.00
29/11/99
00175
T00008
Stage09 - Reset
00175
T00008
New Record 003014
false
Stage01 31/07/99 <= 31/12/99
false
Stage08 - ......
Note the difference in the last two lines. If you do not have a suitable editor, then you should be able to paste into suitable columns on a spreadsheet. Make sure you start each list from the same row though! In both cases, just scan down the lines until you spot a difference.

Rather than prepare a demo program and table I suggest that you paste the SET TALK and SET ALTERNATE lines either side of a suitable loop in one of your own programs. I think you will find the output more meaningful than looking through an example that has no relevance to yourself.

Conclusion

It is easy to become immersed in all the new Visual tools and methods and so start to forget some of the now apparently redundant commands. Indeed anyone whose first experience of dBASE has been with a Windows version might never have noticed them. Although they are now less likely to be relevant to normal use in your applications, they can still, as I hope I have illustrated, prove extremely useful at other times. I do wonder how I would have found the very obscure problem I have just encountered without such a facility.

Oh, and do not forget to delete all those extra lines before you send your client a revised program!


To visit the UK User Group Web site click here.