The debugging topics of this textbook draw substantially on The Debugging Book by Andreas Zeller et al., in particular its terminology of defects, infections, and failures, and its treatment of debugging as an application of the scientific method. Further material was adapted from the debugging readings of MIT 6.102, UC Berkeley CS61B, Stanford CS107, UW CSE 332, and CMU 15-213.
Debugging is the process of finding the cause of a known problem in a program, and fixing it. It starts after you know something is wrong — whether a test, a user, or monitoring exposed it; finding that the problem exists is a separate activity. The hard part is usually the diagnosis rather than the correction: once you understand why a program misbehaves, the edit itself is often a single character — though choosing which edit is a decision in its own right.
To debug well, distinguish four things that beginners tend to lump together as 'the bug':
1.These four form a chain, and each link can be far from the next:
mistake → defect → infection → infection → ... → failure
(code) (state) (state) (behavior)
Debugging is therefore a search, not a lookup: you observe the failure but must fix the defect. The infection spreads as the wrong value is passed on, stored in a field, or used to compute another wrong value, so where the program crashed is usually not where the mistake was made. The chain also explains why bugs hide: a defect infects the state only when that line executes, and an infection becomes a failure only if it propagates out to something observable. A defect can sit in daily-executed code for months unnoticed.
The chain assumes the fault lies in code — the common case, but not the only one. A failure can equally originate in configuration, in data left by an earlier version, in a dependency, in the deployment, or in a requirement that was wrong to begin with. Sometimes the program is right while the test is wrong — the test may hold its own defect, or the expectation it encodes may never have been correct. Only an error in the code is a defect; where the fault lies elsewhere, call it the cause. Either way the search is the same: locate whatever has to change.
Example A running example, reused throughout the related debugging topics of this textbook. A shopping cart prints the correct total, but then appears empty.
class Cart {
private final List<Item> items = new ArrayList<>();
void add(Item item) {
items.add(item);
}
List<Item> getItems() {
return items;
}
int computeTotal() {
int total = 0;
List<Item> pending = getItems();
while (!pending.isEmpty()) {
total += pending.remove(0).price();
}
return total;
}
}
Take the cart's intended contracts to be these: computing a total must not change the cart, and getItems() lets callers read the items without owning the list.
getItems() hands back a copy.getItems() hands out the live internal list, which computeTotal() then empties — breaking both contracts at once.computeTotal() returns, the cart's own items list is empty.A debugger stopped at the failure would be pointing at the display code, which is correct.
Debugging consumes a large share of real development effort, and a single stubborn defect routinely costs more than writing the code it hides in. Beginners tend to read time spent debugging as evidence that they are bad at programming. It is not; debugging is a distinct and learnable engineering skill.
Three things make it hard:
Debugging time is therefore not proportional to the size of the fix. A one-character correction can cost an afternoon. The cost lives in the search, and every debugging technique aims at making that search cheaper.
Most unproductive debugging comes from having no method, rather than from using the wrong tool.
catch block.Adding temporary print statements is not necessarily bad; doing so without a hypothesis is — that is shotgun debugging in another form. A few prints chosen to answer a specific question are legitimate, and in production, embedded, or concurrent settings they are sometimes the only tool available.
What these have in common is that they produce activity without producing information. A productive debugging step is one that rules something out.