Deskchecking and Debugging Output

Last modified: Jun 11, 2020
Contents:

Basic techniques of debugging.

1 The Program


The Program

The program we will work with computes some basic statistics on plain text.

Enter some text, terminated by an empty line:
In this lab, we will look at the basic use of an 
automated debugger.  The program we will work with 
computes some basic statistics on plain text.

There are 27 words in 2 sentences.
The average word length is 4.3 characters.

The Code

The main routine (textstats.cpp) looks like this:

textstats.cpp
#include <iomanip>
#include <iostream>
#include <string>

#include "words.h"


using namespace std;


int main (int argc, char** argv)
{
  initializeWords(10000);

  cout << "Enter some text, terminated by an empty line:" << endl;
  string line;
  getline (cin, line);
  while (line != "")
    {
      addText (line);
      getline (cin, line);
    }

  cout << "There are " << getNumberOfWords()
       << " words in " << getNumberOfSentences()
       << " sentences." << endl;

  double avg = ((double)getNumberOfAlphabeticCharacters()) / getNumberOfWords();

  cout << "The average word length is "
       << setprecision(1) << fixed << avg
       << " characters." << endl;


  return 0;
}

words.h

The functions used in that code are declared in words.h:

#ifndef WORDS_H
#define WORDS_H

#include <string>

void initializeWords (int MaxWords);

void addText (std::string lineOfText);

int getNumberOfWords();

int getNumberOfSentences();

int getNumberOfAlphabeticCharacters();


#endif

words.cpp

and defined in

words.cpp
#include "words.h"
#include <cstdlib>
#include <sstream>

using namespace std;

int numWords;

string* words;

void initializeWords (int MaxWords)
{
  words = new string[MaxWords];
  numWords = 0;
}

void addText (std::string lineOfText)
{
  istringstream in (lineOfText);
  while (in >> words[numWords])
    ++numWords;
}


int getNumberOfWords()
{
  return numWords;
}

int getNumberOfSentences()
{
  int numSentences = 0;
  for (int i = 0; i < numWords; ++i)
    {
      char lastChar = words[i][words[i].size()-1];
      if (lastChar == '.' || lastChar = '?' || lastChar == '!')
        ++numSentences;
    }
  return numSentences;
}


int getNumberOfAlphabeticCharacters()
{
  int numChars = 0;
  for (int i = 0; i < numWords; ++i)
    {
      for (int k = 0; k < words[i].size(); ++k)
        {
          char c = words[i][k];
          if ((c  >= 'A' && c <= 'Z') || (c  >= 'a' && c <= 'z'))
            ++numChars;
        }
    }
  return numChars;
}

2 Debugging Output

Something’s Wrong

Let’s suppose that we have run this program and discovered that the values being printed for the average word length are incorrect. Looking that relevant portion of the main routine:

double avg = ((double)getNumberOfAlphabeticCharacters()) 
                      / getNumberOfWords();

cout << "The average word length is "
     << setprecision(1) << fixed << avg
     << " characters." << endl;

We can see that there are two different functions that contribute to this average.


Which Value is Wrong

cerr << "getNumberOfAlphabeticCharacters(): " 
     << getNumberOfAlphabeticCharacters() << endl;
cerr << "getNumberOfWords(): " 
     << getNumberOfWords() << endl;
double avg = ((double)getNumberOfAlphabeticCharacters()) 
                      / getNumberOfWords();

cout << "The average word length is "
     << setprecision(1) << fixed << avg
     << " characters." << endl;

Once we see the output of these two statements, we can determine which function needs to be explored further.


cerr

For debugging output, we usually write to cerr (the standard error stream) rather than to cout (the standard output stream).


Refactoring

Sometimes we can make it easier to add debugging output by refactoring, rearranging the code slightly in a way that should not affect what it does.

For example, it’s easier to add debugging output if we refactor this:

double avg = ((double)getNumberOfAlphabeticCharacters()) 
                      / getNumberOfWords();

cout << "The average word length is "
     << setprecision(1) << fixed << avg
     << " characters." << endl;

into

int numChars = getNumberOfAlphabeticCharacters();
int numWords = getNumberOfWords();
double avg = ((double)numChars) / numWords;

cout << "The average word length is "
     << setprecision(1) << fixed << avg
     << " characters." << endl;

and then adding the debugging output:

int numChars = getNumberOfAlphabeticCharacters();
int numWords = getNumberOfWords();
cerr << "numChars: " << numChars << endl;
cerr << "numWords: " << numWords << endl;
double avg = ((double)numChars) / numWords;

cout << "The average word length is "
     << setprecision(1) << fixed << avg
     << " characters." << endl;

Where to From There?

Once we determine which function if giving incorrect answers, we can add debugging output there until we have tracked down the problem.

Tips:


Tips (cont.)

getNumDebug0.cpp
int getNumberOfAlphabeticCharacters()
{
  int numChars = 0;
  for (int i = 0; i < numWords; ++i)
    {
      for (int k = 0; k < words[i].size(); ++k)
        {
          char c = words[i][k];
          if ((c  >= 'A' && c <= 'Z') || (c  >= 'a' && c <= 'z'))
            ++numChars;
        }
    }
  return numChars;
}

to this:

getNumDebug.cpp
int getNumberOfAlphabeticCharacters()
{
  int numChars = 0;
  for (int i = 0; i < numWords; ++i)
    {
      for (int k = 0; k < words[i].size(); ++k)
        {
          char c = words[i][k];
          if (i == 3) cerr << "Before: c=" << c << "  numChars=" << numChars << endl;
          if ((c  >= 'A' && c <= 'Z') || (c  >= 'a' && c <= 'z'))
            ++numChars;
          if (i == 3) cerr << "After: c=" << c << "  numChars=" << numChars << endl;
        }
    }
  return numChars;
}

3 Desk Checking

In desk checking, we draw a “picture” of the input data, then step through the code, line by line, updating the picture as the data values change

Desk checking is also useful for

3.1 Example of Desk-Checking

Let’s desk check the getNumberOfSentences() function:

getNumSent.cpp
int getNumberOfSentences()
{
  int numSentences = 0;
  for (int i = 0; i < numWords; ++i)
    {
      char lastChar = words[i][words[i].size()-1];
      if (lastChar == '.' || lastChar = '?' || lastChar == '!')
        ++numSentences;
    }
  return numSentences;
}

Start with a “picture” of some input data:

numWords: 3
words: "Hello.", "What's", "up?"

Then go line by line though the code, updating our picture.


Desk Checking, Line by Line

Starting here:

numWords: 3
words: "Hello.", "What's", "up?"

Change our picture to reflect the effect of the first line of code.

int numSentences = 0;
numWords: 3
words: "Hello.", "What's", "up?"
numSentences: 0

Then move to the next line of code:

for (int i = 0; i < numWords; ++i)
numWords: 3
words: "Hello.", "What's", "up?"
numSentences: 0
i: 0

The next line is

char lastChar = words[i][words[i].size()-1];
numWords: 3
words: "Hello.", "What's", "up?"
numSentences: 0
i: 0
lastChar: '.'

The next line is

      if (lastChar == '.' || lastChar = '?' || lastChar == '!')

This condition is true. Our data picture does not change.

Moving forward, …

       ++numSentences;
numWords: 3
words: "Hello.", "What's", "up?"
numSentences: 0 1
i: 0
lastChar: '.'

Here, for the first time, we had a variable change value instead of simply adding new variables with initial values. If we were doing this with paper and pen, we would simply strike out the old value and write in the new one.

Sometimes I do desk-checking with a text editor, in which case I would overwrite the old value with the new one.


Keep On Checkin’

How long do we keep this up?

A great deal depends on the goal we had in mind when we first started desk-checking.

3.2 Tips on Desk Checking

3.3 Walk-Throughs

In some companies, programmers are required to desk check as a team. A desk check in front of witnesses is often called a walk-through. (Some people use the term for ordinary, solitary desk checking as well.)