Ideally, you should test each routine with every combination of variables possible, this is especially true when you are asking users to enter information. It's little use turning to the person that just crashed your beloved masterpiece to frustratedly exclaim, "Well, what did you press that key for anyway?". Your program should handle both invalid and valid inputs.
One of the things that separates good programmers from indifferent ones is the ability to seek and destroy bugs in code. (An ability to admit that you're wrong sometimes doesn't go amiss either!) Largely this skill is won through experience and downright pig-headedness: it's only a machine, I won't let it beat me. There are several techniques that can help here. In this section, we'll cover some. Please keep in mind that the list is not exhaustive. Every bug will have a slightly different solution and sometimes you just have to invent your own methods.
This discussion will completely ignore the syntax errors that occur when you misspell a variable name or keyword and BASIC squeals 'Mistake at line 1230'. What we're concentrating on is when the program runs smoothly enough, but just won't behave quite as expected.
The first tool in our armoury is our old friend PRINT. Not sure why your WHILE loop never exits? Put a PRINT in the loop with the variables that dictate the exit condition and watch the results scroll up the screen. There are times when too many PRINTs can spoil the display that should be on the screen, or the data changes too fast to see. In either of these cases you can use PRINT TAB to put the results somewhere out of the way and then have a dummy input line which stalls the program giving you control of when the data changes.
Line 5 just waits for any keypress and throws it away, thus stalling the loop. You can make this conditional in order to save key presses and time.
REM Pausing a loop FOR I%=1 TO 10 REM spaces at end overwrite previous data PRINT TAB(0,24);I%;" " Dummy$=GET$ NEXT I% END
The other command that can be embedded in our code is TRACE. TRACE is used with ON and OFF. When BASIC comes across TRACE ON, it will print every line number in square brackets until it encounters TRACE OFF or the program ends. Obviously, you need a program with line numbers to use this. Don't depend on seeing anything that the program is supposed to print out here because it'll zip past in a blur of line numbers. This command is more useful when you want to trace the path that the program takes for a given set of variables.
REM Pausing a loop FOR I%=1 TO 10 IF I%>5 AND I%<8 THEN PRINT TAB(0,24);I%;" " Dummy$=GET$ ENDIF NEXT I% END
TRACE will only display the line number when the line is first executed. If you have a loop in a line like this:
10 REM TRACING a program 20 TRACE ON 30 FOR I%=1 TO 10 40 PRINT TAB(0,24);I%;" " 50 NEXT I% 60 TRACE OFF 70 END
The line will only show once in the trace despite the fact that BASIC is pounding round the loop looking for a keypress.
120 REPEAT : UNTIL INKEY$(0)
TRACE will also accept a line number as a parameter. The idea being that it will only print line numbers less than the one specified. If you write programs as is described in the final section of this tutor, all the PROCs and FNs come after the END, so by using the line number of the END instruction, you will get a view of the overall structure of the program.
I'm not giving too many examples here as the BBC BASIC editor has an excellent debugging tool that eclipses the above methods. Sometimes, even if it's just for inspiration, it's useful to have some other tricks up your sleeve and the above two have been around since BBC BASIC was first written.
As you may be aware, it is possible to see the editor when a program is running. Go to the Windows task bar at the foot of the screen and there it is - back in a click. We can't edit the code when it's running, but the menu still has options that are available to us whilst the program is in progress. From the Utilities menu, select List Variables. This opens a new window which contains a list of all the variables in use at the present time, along with their current values. Knowing this, you can watch a program run without inserting PRINT statements. Very useful.
The window can be resized or scrolled as you'd expect. LOCAL variables are also added to the list when a PROC or FN is entered, along with the name of the PROC or FN, so you can see where you are. If you have duplicate names, e.g. two PROCs that have local variables with the same name, the name only appears once in the list even though there are actually two variables in memory. The list just shows the one that's currently in scope.
There are several buttons in the same block as the immediate button. You have probably used the run button (large black arrow) and possibly the stop (black square). We'll now cover the other two, but first shall we have a program to test with?
Here is a trivial bug. When PROC_Oops runs, it declares a local I, but sets the global I% to 10, hence the loop ends after just one iteration. We will now use the debugger to track this fault.
REM Debug demo FOR I%=1 TO 10 PRINT I% PROC_Oops NEXT I% END DEF PROC_Oops LOCAL I I%=10 ENDPROC
First we enable the Trace option from the Utilities menu by clicking on it. This is different to the TRACE command described earlier. When our program is run, the line currently being executed is highlighted in the editor. Try it. The immediate window opens and in the editor the END line is highlighted. As the program runs faster then we can see, that's not very helpful. The show's over before you can blink.
There is another option that allows us to go through the program one line at a time. To demonstrate this, put the editor's cursor on line 3, with the PRINT statement; it doesn't matter where. This time, instead of pressing Run, select the Run menu and click 'Run to cursor'. The Pause button on the toolbar is shown pressed and the Step button comes to life. Every time we press the Step button the program advances to the next line and waits. By stepping through, we can verify that the loop is executed just once. Do this and notice that we lose the Pause when the program ends.
Put the cursor back on line 3. Select 'Run to cursor' and when the pause becomes active, open the variable list. Step through again. When the program gets to PROC_Oops it creates a local called I, you can see it appear in the list. Stepping through into PROC_Oops, we see I% change value, not I as was expected. Round about now the cause of the bug hits us so we don't need to single step anymore. By clicking Pause, step is released allowing the program to end at normal speed. We can now correct our little bug, feel really pleased with ourselves and then mentally chastise ourselves for putting it there in the first place.