Reading: Debugging your programs

Read By
Monday, Oct 29, 2018
Summary
We consider how to trace the execution of your Scheme programs so that you can better identify and correct potential errors in those programs.

Introduction

It’s hard to write correct programs. It’s especially hard to write correct programs on the first (or second, or third) try. You’ve probably figured that out by now. You’ve probably even seen your instructor make some mistakes while trying to write programs “live” during class. And, as you may have discovered, there are a lot of ways in which a program can be “wrong”. The programmer may have designed an incorrect algorithm and not realize it. The programmer may have incorrectly translated the algorithm into Scheme. The programmer may misunderstand how a particular Scheme operation works. The programmer may have missed some edge or corner cases. The list goes on and on.

Just as there are a wide variety of things that can go wrong with your program, there are also a wide variety of approaches that one takes to fix programs. You’ve seen one important technique: “Talking through” your code with another person, often with an example in mind, helps you ensure that your algorithm is correct, that you’ve correctly translated it into Scheme, and that you’ve understood the built-in and library procedures you are using. You may have also realized that it’s possible to debug a more complex piece of code by breaking it into smaller pieces and making sure that each piece works correctly.

You should also have found that DrRacket can help you identify syntax errors or parenthesization mistakes, such as by telling you when it doesn’t know a name you’ve used or by showing you through indentation how things are nested.

Of course, to fix code you also need to know when there are problems with code. Our early exploration of testing suggests one approach: You design a variety of tests and think about edge and corner cases, and you do so before you start to write the code. Once again, “talking through” things can help.

But what do you do once you have good tests and you’ve manually stepped through the code to make sure that it seems to do what you think it should? At that point, some programmers put a lot of output statements (like display) in their program. However, good programmers more often turn to a debugger, a program that lets you step through the program and check the state of the system as you go. In this reading, we explore DrRacket’s debugger.

Core aspects of debuggers

As you explore different programming languages and program development environments, you will find that most debuggers have a few core aspects.

  • Debuggers let you inspect variables. Some will require an explicit command to show the value of a variable. Others will automatically show the values of all relevant variables and parameters.
  • Debuggers let you set (and remove) breakpoints, places in the code in which you want to pause execution.
  • Debuggers let you advance through the code in multiple ways.
    • You can step through the code, line by line.
    • You can advance to the next breakpoint.
    • You skip over a procedure call or expression (rather than see what happens within that procedure call).
    • You can skip out of a procedure call or expression.
  • Debuggers show you the stack of procedure calls (or, in some cases, nested expressions). As with the case of variable, some debuggers will show you the stack explicitly while others will require that you enter an additional command to see the stack.

Some debuggers have a graphical user interface (GUI). Others are based on text that you type. Different users and different situations call for different kinds of debugging.

The DrRacket Debugger

Now that you know what to look for, let’s take a look at the DrRacket debugger. You bring up the debugger by clicking the Debug button, rather than the Run button. Once you click that button, you should see four panes.

The DrRacket debugger.  The traditional definitions and interactions pane in the DrRacket user interface have been supplemented by a small pane on the upper right labeled "Stack" and a small pane on the lower right labeled "Variables".  Five new buttons appear over the definitions pane.  They are labeled "Pause", "Go", "Step", "Over", and "Out".  The "Pause" and "Over" buttons are currently greyed out.  The definitions pane includes a definition of `average-w/o-extremes` and several helper procedures.  A green triangle and green circle highlight an expression within `average-w/o-extremes`.  A red circle appears at the beginning of one of the expressions within `average-w/o-extremes`.  Further aspects of this diagram are described in the subsequent paragraphs.

The DrRacket debugger supplements the standard DrRacket user interface with two more panes, labeled “Stack” and “Variables”, as well as five buttons to step through the program. The Stack pane contains a sequence of procedure calls. You can click on any of them to see the values of the variables and parameters relevant to that call.

You will note a green arrow and circle in the body of average-w/o-extremes. These represent the expression that is currently being evaluated. You should also note the red circle in the body of average-w/o-extremes. That represents a breakpoint.

As you might expect, debuggers make more sense when you use them, rather than read about them. So stay tuned for lab!

Systematic debugging

Debuggers do not fix your code for you. They don’t even automatically identify your bugs. What they do is help you better understand what your code is doing. Hence, to be successful with your debugger, you need to be systematic in your debugging. Just as you would when working through the program with a partner, you need to identify what order of evaluation you expect your program to follow and what values it computes along the way. You then run through the program to see if it matches your predictions and, if not, you’ve identified a likely place where there is an error in the code.

Like programming, documentation, and testing, systematic debugging takes time and practice to master.

Self checks

Check 1: Recovering from bugs

We’ve described a number of techniques (other than a debugger) that a programmer may use upon encountering an error. What strategy do you use when your program doesn’t work?

Check 2: Printing vs debugging

In most cases, we discourage students from using display statements instead of a debugger (at least once they’ve learned the debugger). Why might a debugger be a better way to figure out what’s going wrong with your program?

Further reading

Robert Bruce Findler and PLT (n.d.). The DrRacket Graphical Debugging Interface. In DrRacket: The Racket Programming Environment. Available online at https://docs.racket-lang.org/drracket/debugger.html.