Debugging Lab

When you hear the person sitting next to you saying “I have a bug in my program”, you don't have to worry about roaches infesting the computer. When we say bugs we really mean logical errors in our program. The first bug, however, was a real live bug. Well, live at first. In 1947 at Harvard University, a moth was found in one of the components of the Mark II computer, and was causing problems. 

.

In this lab you will deal with debugging. You will practice locating and fixing some common types of bugs. The high point of the lab is a tour of the debugger inside the BlueJ environment.

A. Reading Stack Traces

Make a BlueJ project from the files WordAnalyzer.java and WordAnalyzerTester.java.

Have a look at the WordAnalyzer class. A WordAnalyzer is constructed with a string—the word to be analyzed. Right now, we only care about the first (buggy) method: firstRepeatedCharacter. It returns the first repeated character in a word, such as o in roommate.

The WordAnalyzerTester program simply tests the WordAnalyzer class.

Step 1
Without actually running the program, predict its output. Assume that the firstRepeatedCharacter method works correctly.

Step 2
Now run the program. What output do you get? (Include the correct outputs and the error message.)

The program dies with an exception and prints a stack trace:

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 4
at java.lang.String.charAt(String.java:558)
at WordAnalyzer.firstRepeatedCharacter(WordAnalyzer.java:26)
at WordAnalyzerTester.test(WordAnalyzerTester.java:14)
at WordAnalyzerTester.main(WordAnalyzerTester.java:7)

Have a look at the stack trace. The first complaint is about the method charAt of the java.lang.String class. That's a library class. It is extremely unlikely that a library class has a bug. It is far more common that one of its methods was called with bad parameters.

The next complaint is about line 26 of the firstRepeatedCharacter method of the WordAnalyzer class.

Step 3
Exactly what source code do you find in line 26 of WordAnalyzer?

Now look at the call to charAt in line 26.

word.charAt(i + 1)

Step 4
In theory, there are two different exceptions that can be thrown in this call. What are they? 

Of course, we know that word is not null in this case, so we know that the error must be the index. 


Step 5
(a) Explain the nature of the bug, and how to fix it. (b) What output did you get after you fixed the bug?

B. Logging

Exceptions are useful to pinpoint drastic errors that kill your program. But often, a sick program doesn't die. It limps along and computes the wrong result. In order to find out what such a program does, students often sprinkle their code with print statements like this one:
System.out.println("Yoohoo! I got this far!");
. Print statements can waste a lot of your time. You put them in, you take them out, you put them back in, you comment them out, you remove the comments, and when it all works you take them out for good. Until the next bug appears.

In this section, you will practice the use of logging statements. Logging is easy. Instead of System.out.println, simply use Logger.global.info. For example,
Logger.global.info("About to return from find. i=" + i);
Logging is better than System.out.println for two reasons:
Look at WordAnalyzerTester2. It tests the firstMultipleCharacter method of the WordAnalyzer class. That method also has a bug.

Step 1
Which input to the firstMultipleCharacter method does not yield the expected result?

We will use logging to find the problem. Add the following statements at appropriate places of the find method:
Logger.global.info("Entering find. c=" + c + ",pos=" + pos);
Logger.global.info("About to return from find. i=" + i);

The Logger class is in the java.util.logging package.

Don't worry if you get a warning that Logger.global.info is deprecated. Your program will compile and run correctly. If it really bothers you, use Logger.getLogger("global").info instead. In Java 7, you can use Logger.getGlobal().info.

Step 2
What is the code of your find method?

Step 3
What output do you get when you run the WordAnalyzerTester2 class?

Step 4
Look at the logging messages. Explain why the  firstMultipleCharacter method does not work.

Step 5
You can fix the problem by modifying either the  firstMultipleCharacter method or the find method. Fix the problem. What fix did you make? 

Step 6
Run the program again. What output do you get now? 

Of course, now you no longer need the logging messages. To turn them off, add this statement to the main method in WordAnalyzerTester2:
Logger.global.setLevel(Level.OFF);
Step 7
Add that statement and run the program again. What output do you get now? 

Step 8
Suppose you find another bug and want to turn logging back on. How do you do that? 

In a small program, using Logger.global.info works fine. As your programs grow larger, you can control your logging in more sophisticated ways. You can use different logging levels. Instead of info, call methods severe for important messages or fine for "fine-grained" messages. Then call the setLevel method to set the level of the messages that you want to see in a particular program run. You can also define your own logger objects in addition to Logger.global.  Keep this in the back of your mind; it will come in handy in the future.

C. Running a Debugger

Most development environments, including BlueJ and NetBeans, have a debugger, a tool that lets you execute a program in slow motion. You can observe which statements are executed and you can peek inside variables. 

Debuggers can be complex, but fortunately there are only three techniques that you need to master:

  1. Set breakpoint
  2. Single step
  3. Inspect variable
In this lab, we will use the debugger that is a part of BlueJ.

For this section, you need the WordAnalyzerTester3 class that tests the buggy countRepeatedCharacters method. That method counts the substrings consisting of repeated character in a word. For example, the word mississippiiihas a count of 4. 

Step 1
Execute WordAnalyzerTester3 in BlueJ. What output do you get?

The countRepeatedCharacters method does the right thing for the first two test cases, but it mysteriously fails on the string "aabbcdaaaabb". It should report 4 repetitions, but it only reports 3.  

Setting Breakpoints

Unfortunately, the debugger cannot "go back", so you can't simply go to the point of failure and backtrack. Instead, you first run your program at full speed until it comes close the point of failure. Then you slow down and execute small steps, watching what happens. 

We know that the first two calls to the test method in WordAnalyzerTester3 give the correct results. It won't be too interesting to debug them. Instead, let's go directly to the third call. We'll set a breakpoint at that line, so that the debugger stops as soon as it reaches the line.

In BlueJ, open the WordAnalyzerTester3 class for editing. Click left of the vertical line in the line containing the third call to test. A red stop sign should appear, indicating the breakpoint.

.

Now run the program. When it hits the first breakpoint, the program stops and the current line is highlighted:

Step 2
Launch the debugger as described. What output do you get in the console window? Why do you get two lines of output and not three? 

Single stepping

When the debugger stops at a breakpoint, another window appears, with a row of big buttons on the bottom:

You use the Step and Step Into buttons to step through your code in slow motion.

Step 3
Execute the "Step Into" command. What happens? 

Now you are inside the test method. The next call is
WordAnalyzer wa = new WordAnalyzer(s);
That call is boring, and we do not want to step into the constructor. We'll use the "Step Over" command instead.

Step 4
Execute the "Step" command. What happens? 

Now you are at the line
int result = wa.countRepeatedCharacters();

Step 5
Remember, we want to find out why we get the wrong repetition count for the third test case. Should you execute "Step" or "Step Into" at this point? Why? 

Inspecting Variables

Execute "Step Into" a couple of times. You should get into the lines

int c = 0;

and

for (int i = 1; i < word.length() - 1; i++)

Look inside the Local variables window in the bottom right corner.



Now keep executing the Step command. Watch what happens to the c and i values. You'll see i increase each time the loop is executed.

Step 6
The value of c increases three times. What are the values for i at each increase? 

Step 7
Look at the value of the word instance variable. What is special about the three positions at which c increases?  

We will fix the bug in the next section. For now, click the Continue button. Your program runs again at full speed, until it hits the next breakpoint or until it terminates.

Step 8
What happens when you click the "Continue" button? 

You now know how to use the debugger. You have learned how to set breakpoints, how to single-step, and how to view variables.

Fixing the bug

The countRepeatedCharacters method looks for character sequences of the form xyy, that is, a character followed by the same character and preceded by a different one. That is the start of a group. Note that there are two conditions. The condition
if (word.charAt(i) == word.charAt(i + 1)) 
tests for yy, that is, a character that is followed by another one just like it. But if we have a sequence yyyy, we only want it to count once. That's why we want to make sure that the preceding character is different:
if (word.charAt(i - 1) != word.charAt(i))
This logic works almost perfectly: it finds three group starts: aabbcdaaaabb

Step 9
Why doesn't the method find the start of the first (aa) group? 

Step 10
Why can't you simply fix the problem by letting i start at 0 in the for loop? 
 
Step 11
Go ahead and fix the bug. (a) What is the code of your  countRepeatedCharacters method now? (b) Run the WordAnalyzerTester3 method again. What is the output now?

. Is the program now free from bugs? That is not a question the debugger can answer. As the famous computer scientist Edsger Dijkstra pointed out: "Program testing can be used to show the presence of bugs, but never to show their absence!" As you have seen in this lab, testing and debugging is a laborious activity. In your computer science education, it pays to pay special attention to the tools and techniques that ensure correctness without testing.