Last modified: Jul 15, 2014
Recap – Rules of Debugging
You can’t debug what you don’t understand
Make it fail…
Make it fail reliably
Make it fail easily
Make it fail visibly
Track down the culprit
In this lesson we focus on tracking down the culprit.
An actual message received from a student
Professor Zeil,
Upon running my code, I found several errors that I have been unsuccessful in solving. The most glaring error is that when I try to use the “Tile” option under the view menu, I get an error concerning a division by zero. Looking back at where the error references the code, it seems to be happening at the point where there is division by the bounding box height and width. This leads me to believe that my bounding box function for the Composite is flawed. This is not unlikely considering my inexperience with Java, but by my estimation it should have worked. I used the union function to combine the bounding boxes of different shapes, so there should be no reason that the bounding box returned by the Composite would be zero. Unless there are no shapes in the list to begin with, but that should not be the case either. When I use the “Draw Composite” option under the view menu, shapes are drawn, so there should be shapes in the Composite private member field (I am using a vector to store the shapes). Additionally, when I select the “Move Composite” or “Reflect Composite” options, nothing is displayed, but there is no error message given either. Any assistance you can provide would be greatly appreciated.
This email was sent in regards to an assignment involving manipulating pictures composed of many component shapes.
A key idea in such applications is that of a bounding box, the smallest rectangle that can enclose a set of shapes. Computing the bounding box was one of the first steps required for many of the picture manipulations.
What’s Wrong with That?
But because there is no follow-up, it’s all guesswork.
Wasted Effort
Some of the thought going into those guesses may have been wasted effort:
This leads me to believe that my bounding box function for the Composite is flawed…the bounding box returned by the Composite would be zero.
This whole line of reasoning takes for granted that the problem is a zero bounding box.
And that the problem occurred in the function where the box was first computed.
That’s an awful lot of effort going into something that might not even be remotely true.
Continuing in that vein
I used the union function to combine the bounding boxes of different shapes, so there should be no reason that the bounding box returned by the Composite would be zero. Unless there are no shapes in the list to begin with, but that should not be the case either.
This leads us to the key rule of this lesson …
The problem with the above-quoted student is that he was making guesses instead of hypotheses.
An “hypothesis” is a guess that can be tested to see if it is true or not.
Guessing
Looking back at where the error references the code, it seems to be happening at the point where there is division by the bounding box height and width.
Assumes that this is where the error occurs.
Assumes (guesses) that height and width are interchangeable in this bug
More Guessing
This leads me to believe that my bounding box function for the Composite is flawed.
Guesses that the problem lies in the function that computes the bounding box
Does not consider alternatives
Could something be altering the box later?
Could there be pointer errors overwriting the memory occupied by the bounding box variable?
Still More Guessing
by my estimation it should have worked. I used the union function to combine the bounding boxes of different shapes, so there should be no reason that the bounding box returned by the Composite would be zero.
Unless there are no shapes in the list to begin with, but that should not be the case either.
Won’t the Guesswork Ever Stop?
When I use the “Draw Composite” option under the view menu, shapes are drawn, so there should be shapes in the Composite private member field
Can you phrase that in the form of a question?
Additionally, when I select the “Move Composite” or “Reflect Composite” options, nothing is displayed, but there is no error message given either.
Guesses that this symptom is related to he problem under discussion
As opposed to the above-mentioned ADT data member simply being empty during “move” and “reflect” operations
The frustrating thing about all that guesswork is that it would have been easy to check and see which of those guesses were actually true:
Looking back at where the error references the code, it seems to be happening at the point where there is division by the bounding box height and width.
Guess + Test = Hypothesis
This leads me to believe that my bounding box function for the Composite is flawed.
Why Engage in Idle Speculation?
by my estimation it should have worked. I used the union function to combine the bounding boxes of different shapes, so there should be no reason that the bounding box returned by the Composite would be zero.
Maybe it’s not zero!
So print the bounding box and see if it is zero or not.
Hypothesize
Unless there no shapes in the list to begin with, but that should not be the case either.
Hypothesize, Hypothesize
When I use the “Draw Composite” option under the view menu, shapes are drawn, so there should be shapes in the Composite private member field
Hypothesize, Hypothesize, Hypothesize
Additionally, when I select the “Move Composite” or “Reflect Composite” options, nothing is displayed, but there is no error message given either.
As you trace backwards along the path from failure to possible faults,
In a large system with many components, you do not want to waste time studying all the parts that are working
Narrow the search as quickly as possible
We’ve already discussed the general flow of debugging:
choose the simplest test that reliably illustrates the failure
work backwards from known bad states
Divide and Conquer
If you can find a location along a probable backwards infection path that
Is easily checked for infection
Divides the system/path into roughly equal chunks
then test the hypothesis that the failure occurs before that location.
Examine the state at that location to see if it is infected.
If it is, you don’t have to examine the back half of your system/path.
If it is not, you don’t have to examine the front half of your system/path.
You can save a lot of time and effort by repeatedly cutting your search space in half.
Essentially, this is the “binary search” approach to debugging.
What if a Problem Has Multiple Possible Causes?
If you find that there are multiple potential causes for the observed failure, you may have to test hypotheses for each in turn.
If you find the cause, great!
If not, you have still narrowed the possibilities
(Quoted from Debugging by David Agans)
If an error goes away, and you don’t understand why, don’t trust that it is really gone.
Grounds for Suspicion
If an error goes away, and you don’t understand why, don’t trust that it is really gone.
Is the bug intermittent?
You might merely have altered the input set on which it manifests.
E.g., changing critical “size” constants may defer overflow problems but not eliminate them.
Bugs can hide one another (on some but not all inputs)