Difference between revisions of "Debugging Exercises"

(Add exercise 2)
(Add exercise 3)
Line 75: Line 75:
 
<div class="toccolours mw-collapsible mw-collapsed" style="width:800px">
 
<div class="toccolours mw-collapsible mw-collapsed" style="width:800px">
 
Now that you've walked through the program, do you know ''why'' we got a <code>NullPointerException</code>?
 
Now that you've walked through the program, do you know ''why'' we got a <code>NullPointerException</code>?
<div class="mw-collapsible-content">Although <code>makeAThing</code> does create an new Object, that Object isn't actually returned by the method.</div>
+
<div class="mw-collapsible-content">> Although <code>makeAThing</code> does create an new Object, that Object isn't actually returned by the method.</div>
 
</div>
 
</div>
  
Line 100: Line 100:
 
<div class="toccolours mw-collapsible mw-collapsed" style="width:800px">
 
<div class="toccolours mw-collapsible mw-collapsed" style="width:800px">
 
Try debugging now, using ''Resume'' any time a breakpoint is encountered. How many times do you hit a breakpoint?
 
Try debugging now, using ''Resume'' any time a breakpoint is encountered. How many times do you hit a breakpoint?
<div class="mw-collapsible-content">Three times. This is because we're debugging inside a method that is used multiple times in our program.</div>
+
<div class="mw-collapsible-content">>Three times. This is because we're debugging inside a method that is used multiple times in our program.</div>
 
</div>
 
</div>
  
Line 106: Line 106:
 
Since we are only interested in the <code>processElementAtIndex</code> method when a problem actually occurs, let's try something different:
 
Since we are only interested in the <code>processElementAtIndex</code> method when a problem actually occurs, let's try something different:
  
[[File:E2BreakOnException.png|right|600px|thumb|Setting the breakpoint]]
+
[[File:E2BreakOnException.png|right|600px|thumb|Setting a breakpoint on an exception]]
 
# From the ''Breakpoints'' window in the ''Debug'' perspective, delete the old breakpoint
 
# From the ''Breakpoints'' window in the ''Debug'' perspective, delete the old breakpoint
 
# Now use the ''Add Java Exception Breakpoint'' button to add a breakpoint to ''IllegalArgumentException''
 
# Now use the ''Add Java Exception Breakpoint'' button to add a breakpoint to ''IllegalArgumentException''
 
# Debug the program. When it stops, inspect the ''Variables'' window.
 
# Debug the program. When it stops, inspect the ''Variables'' window.
[[File:E2Variables.png|right|600px|thumb|Variables window]]
+
[[File:E2Variables.png|right|600px|thumb|Inspecting the variables window]]
  
 
At this point, we know there is a problem accessing the <code>99999th</code> element of the list, but the variables window doesn't tell us exactly what the problem is. We can manually expand and explore the <code>list</code> variable - but given its size that could be cumbersome.
 
At this point, we know there is a problem accessing the <code>99999th</code> element of the list, but the variables window doesn't tell us exactly what the problem is. We can manually expand and explore the <code>list</code> variable - but given its size that could be cumbersome.
Line 127: Line 127:
 
<div class="toccolours mw-collapsible mw-collapsed" style="width:800px">
 
<div class="toccolours mw-collapsible mw-collapsed" style="width:800px">
 
Once you've evaluated these expressions, can you tell what went wrong in the program?
 
Once you've evaluated these expressions, can you tell what went wrong in the program?
<div class="mw-collapsible-content">We asked for a list of size 100,000 but a list of size 99,9999 came back. Since we used hard-coded indices, instead of size-relative (e.g. <code>list.size()-1</code>), a method was called wanting to access a non-existent element.</div>
+
<div class="mw-collapsible-content">>We asked for a list of size 100,000 but a list of size 99,9999 came back. Since we used hard-coded indices, instead of size-relative (e.g. <code>list.size()-1</code>), a method was called wanting to access a non-existent element.</div>
 +
</div>
 +
 
 +
 
 +
==Exercise 3: Conditional breakpoints==
 +
 
 +
'''Goals'''
 +
 
 +
* Create a breakpoint that triggers after a specified number of hits
 +
* Create a breakpoint that triggers when a certain condition is true
 +
 
 +
 
 +
 
 +
Start by opening the <code>E3ConditionalCrisis</code> source and running it. This time our console output looks a bit different:
 +
 
 +
[[File:E3StackTrace.png]]
 +
 
 +
 
 +
In addition to the exception stack trace, the program itself appears to have found an invalid object, causing the processing to go unfinished. Although we could set a breakpoint on the exception, as we did in [[#Exercise 2: Expressions|exercise 2]], the exception is actually happening ''after'' the more interesting part of the program - the loop. As we learned in exercise 2, breakpoints in code that is called repeatedly are annoying, so let's see what we can find by attaching conditions to our breakpoint.
 +
 
 +
First set a breakpoint on the line ''after'' the <code>everythingIsOK</code> assignment:
 +
<source lang=java>
 +
everythingIsOK = ObjectAnalyzer.processElementAtIndex(myArray, i);
 +
i++;
 +
</source>
 +
 
 +
Then try the following:
 +
 
 +
[[File:E3CountBreakpoint.png|350px|right|thumb|Setting a hit count]]
 +
# Open the ''Breakpoints'' window
 +
# Right-click our breakpoint and select ''Breakpoint Properties...''
 +
# Check the ''Hit Count'' box and set it to the object number printed in the error message.
 +
# Try debugging
 +
 
 +
 
 +
<div class="toccolours mw-collapsible mw-collapsed" style="width:800px">
 +
Was there a problem with the current object when/if your breakpoint is hit?
 +
<div class="mw-collapsible-content">>If there was, try it again! In this exercise, the "broken" object index is non-deterministic - so it is unlikely to be exactly the same index two runs in a row.<br>>If the broken object appears later the second time, your breakpoint will hit but the current object will likely be fine.<br>>If the "broken" object appears earlier the second time, your breakpoint won't be hit at all.</div>
 +
</div>
 +
 
 +
 
 +
Using <code>count</code>-based conditional breakpoints can be very useful if the error is deterministic. In this case we need to try something different. We know the <code>everythingIsOK</code> flag reflects the integrity of the object at a given index - so what we really want to use here is a breakpoint that stops in the loop when the <code>everythingIsOK</code> flag is set to false:
 +
 
 +
[[File:E3ConditionalBreakpoint.png|350px|right|thumb|Setting a conditional expression]]
 +
# Open the ''Breakpoints'' window again
 +
# Open the properties of our breakpoint
 +
# Uncheck the ''Hit Count'' box
 +
# Check the ''Conditional'' box, and "Suspend when 'true'"
 +
# Enter the condition we want to check (any Java conditional statement can be used here)
 +
# Try debugging again
 +
 
 +
Were you able to get the breakpoint to stop in the loop only when a problem is encountered?
 +
 
 +
 
 +
<div class="toccolours mw-collapsible mw-collapsed" style="width:800px">
 +
What was suspicious about the object at that index?
 +
<div class="mw-collapsible-content">>The object was <code>null</code>.</div>
 
</div>
 
</div>

Revision as of 14:53, 23 November 2015

Template:Development



Debugging is the art of determining the cause and/or location of a problem. The purpose of this guide is to provide developers practical, hands-on experience using a variety of debugging techniques to identify problems in code.

Requirements

As ImageJ is built using the SciJava principles of project management, this guide assumes a basic familiarity with these topics and tools, especially:

Additionally, you should:

Finally, you should read the Debugging in Eclipse section, at least, of Lars Vogel's Eclipse tutorial. Note that there is significant overlap between the topics covered between these tutorials. Vogel's guide does a fantastic job of laying out the debugging interface and tools in Eclipse, while this guide focuses on providing hands-on practice and explaining the reasoning for when to use these tools.

If you find yourself confused when this tutorial asks you to do something in Eclipse (e.g. "start debugging", "step into") it's almost certainly covered in Vogel's guide.

What not to do: print statements

For many developers, the first tool in their debugging toolbox is the print statement. Print statements are easy to lean on as a safety crutch: you don't need any special knowledge to use them, and they often work in identifying answering common questions (e.g. "why is this variable null here?", "how many elements are in my array here?").

However, there are critical drawbacks to trying to debug via print statement:

  • They are slow. If you realize you need to move or add a print statement, you need to recompile your code and re-launch your application.
  • They are part of the code. Adding print statements changes line numbers, causes git to pick up modifications to the source code, and can even affect performance and/or behavior.
  • They aren't even very useful. Even the most basic breakpoint and expression evaluation in Eclipse debug mode gives you vastly more power and flexibility over print statements.

Learning to use debugging tools is, understandably, a burden: it's "one more thing" to learn as a developer. But if you want to develop ImageJ plugins, you will almost certainly run into cases where debugging is necessary. So you may as well start familiarizing yourself with the tools now, gaining skills and perspectives that will serve you well, ideally throughout your career.

Using this guide

Remember the goal of these exercises is not to solve the problems, but to learn how and when to use the myriad of troubleshooting techniques available. Given the inherent complexity that comes with a combined work, all of these techniques have their time and place when working on a rich application like ImageJ.

Because this project is intended to help new developers practice troubleshooting skills, it uses contrived examples, limited in complexity. If you have complete, perfect knowledge of the code then there isn't really a need for troubleshooting, as it is trivial to see why something is behaving incorrectly. Thus the source of these exercises is divided into hidden and visible packages. Users are strongly recommended to only inspect and set breakpoints from the visible classes. From a development point of view, consider the hidden package a 3rd-party library that you may not have control over.

Changing the source code to actually fix changes is outside the scope of this guide, but motivated users are of course welcome to do so for practice.

If at any time you need to revert changes to this repository, you can always do so via the command:

git reset --hard origin/master

Exercises

Exercise 1: Breakpoints

Goals

  • Practice setting breakpoints in Eclipse
  • Use the Variables window to inspect variable values
  • Use the navigation commands to traverse code while in Debug mode


The first thing we want to do is open the source file, E1BasicBreakpoints, and run it to get an idea of what's going on. We should see a simple stack trace:


E1StackTrace.png


Stack traces are a common starting point for debugging, as they indicate something has gone wrong in Java that the program was not prepared to handle. In this case, the stack trace tells us we know the what (tried to use a null object) and the where (the topmost line number), but not why the object was null at that point - the actual root cause of the exception.

To investigate further, try to complete the following debugging steps:

  1. Set a breakpoint in the main method, before makeAThing is called.
  2. Debug the file as a Java application
  3. When the breakpoint is encountered, step in to the makeAThing method.
  4. step out of the makeAThing method.
  5. In the Variables window, look at the value of the Object variable.
  6. 'resume' execution until the program completes


Now that you've walked through the program, do you know why we got a NullPointerException?

> Although makeAThing does create an new Object, that Object isn't actually returned by the method.


Exercise 2: Expressions

Goals

  • Set breakpoints on exception creation
  • Use the Expressions window to evaluate code at runtime


Start by opening the E2EffectiveExpressions source and running it. Like the previous exercise, we have a stack trace to start from:


E2StackTrace.png


Try setting a breakpoint on the conditional line:

if (index < 0 || index >= list.size()) {


Try debugging now, using Resume any time a breakpoint is encountered. How many times do you hit a breakpoint?

>Three times. This is because we're debugging inside a method that is used multiple times in our program.


Since we are only interested in the processElementAtIndex method when a problem actually occurs, let's try something different:

Setting a breakpoint on an exception
  1. From the Breakpoints window in the Debug perspective, delete the old breakpoint
  2. Now use the Add Java Exception Breakpoint button to add a breakpoint to IllegalArgumentException
  3. Debug the program. When it stops, inspect the Variables window.
Inspecting the variables window

At this point, we know there is a problem accessing the 99999th element of the list, but the variables window doesn't tell us exactly what the problem is. We can manually expand and explore the list variable - but given its size that could be cumbersome.

Instead, let's use expressions. If it's not already visible, open the Window > Show View > Expressions view.

In this view, we can add any number of Java expressions to evaluate. We can call methods from, and on, any variables and classes visible from the current scope.

Warning: if you evaluate expressions that change the state of variables, for example List.add, then those changes will persist. Sometimes you do legitimately need to alter the state of variables while debugging (for example, to force the conditions in which a bug appears). Just be aware that you could also be introducing problematic behavior when evaluating expressions.

To complete this exercise:

  1. Write an expression that calls the size() method of our list.
  2. Write a 2nd expression that evaluates to the value of the index variable


Once you've evaluated these expressions, can you tell what went wrong in the program?

>We asked for a list of size 100,000 but a list of size 99,9999 came back. Since we used hard-coded indices, instead of size-relative (e.g. list.size()-1), a method was called wanting to access a non-existent element.


Exercise 3: Conditional breakpoints

Goals

  • Create a breakpoint that triggers after a specified number of hits
  • Create a breakpoint that triggers when a certain condition is true


Start by opening the E3ConditionalCrisis source and running it. This time our console output looks a bit different:

E3StackTrace.png


In addition to the exception stack trace, the program itself appears to have found an invalid object, causing the processing to go unfinished. Although we could set a breakpoint on the exception, as we did in exercise 2, the exception is actually happening after the more interesting part of the program - the loop. As we learned in exercise 2, breakpoints in code that is called repeatedly are annoying, so let's see what we can find by attaching conditions to our breakpoint.

First set a breakpoint on the line after the everythingIsOK assignment:

everythingIsOK = ObjectAnalyzer.processElementAtIndex(myArray, i);
i++;

Then try the following:

Setting a hit count
  1. Open the Breakpoints window
  2. Right-click our breakpoint and select Breakpoint Properties...
  3. Check the Hit Count box and set it to the object number printed in the error message.
  4. Try debugging


Was there a problem with the current object when/if your breakpoint is hit?

>If there was, try it again! In this exercise, the "broken" object index is non-deterministic - so it is unlikely to be exactly the same index two runs in a row.
>If the broken object appears later the second time, your breakpoint will hit but the current object will likely be fine.
>If the "broken" object appears earlier the second time, your breakpoint won't be hit at all.


Using count-based conditional breakpoints can be very useful if the error is deterministic. In this case we need to try something different. We know the everythingIsOK flag reflects the integrity of the object at a given index - so what we really want to use here is a breakpoint that stops in the loop when the everythingIsOK flag is set to false:

Setting a conditional expression
  1. Open the Breakpoints window again
  2. Open the properties of our breakpoint
  3. Uncheck the Hit Count box
  4. Check the Conditional box, and "Suspend when 'true'"
  5. Enter the condition we want to check (any Java conditional statement can be used here)
  6. Try debugging again

Were you able to get the breakpoint to stop in the loop only when a problem is encountered?


What was suspicious about the object at that index?

>The object was null.