09 · Hypothesis, then tests
When you finish this lesson you should be able to:
- Debug with a spoken hypothesis instead of random prints
- Notice when you are programming by coincidence
- Add a failing case while the behavior is in your head, not at the end
Core idea
Section titled “Core idea”Guessing is not a process. Debugging is: observe, hypothesize, predict, check, repeat. “I think parseLine drops the last field when there is no trailing newline. Next I will feed one line without \n and log the split length.” If you cannot say what you will look at, you are thrashing.
Programming by coincidence is the cousin: it worked once, so you leave it. The array happened to be sorted. The test happened to run in UTC. Coincidence is a bug that has not met the right input.
Tests are how you pin down the hypothesis. Test as you go: the moment you handle duplicates, write the duplicate case. Property-style thinking helps even without a library—“for any list, output length ≤ input length of unique ids.” You do not need a framework speech. You need a named example and an assertion.
If you are stuck for a few minutes, say it: hypothesis, smaller example, then ask. Silence while you scroll is expensive. A wrong hypothesis spoken is still progress; someone can falsify it.
Example
Section titled “Example”Prompt: collapse events into the latest status per messageId. Tests show a failure on a hidden case.
Coincidental code: events.sort() then walk. Works if timestamps are unique and sort is stable in this runtime. Fails when two events share at and the spec says “later in the file wins.”
Hypothesis: “I’m ordering by time only, so same-timestamp pairs follow sort’s mood.” Check: two-line fixture, identical at, different status. Fix: use input index as a tie-breaker. Leave the fixture in the file.
function latestStatusByMessage(events) { const latest = new Map(); events.forEach((event, index) => { const prev = latest.get(event.messageId); if (!prev || event.at > prev.at || (event.at === prev.at && index > prev.index)) { latest.set(event.messageId, { ...event, index }); } }); return latest;}You did not “add logging until it worked.” You named the rule, proved it on a small example, and kept the example.
Drill (12 minutes)
Section titled “Drill (12 minutes)”No autocomplete. Talk.
- Bug report:
countActive(flags)returns 1 when a flag isonfor US andoffelsewhere, user is in FR. Expected 0. - State a hypothesis in one sentence.
- Invent the smallest fixture that would prove or kill it.
- Say the next check if that fixture passes anyway.
- Write one test name that would have caught this (
active_respects_country_ruleor similar).
Then, on a blank file, implement latestStatusByMessage for four events, including a tie on at. Narrate edges as they appear.
Further reading
Section titled “Further reading”- The Pragmatic Programmer — Topic 20, Debugging
- The Pragmatic Programmer — Topic 38, Programming by Coincidence
- The Pragmatic Programmer — Topics 41–42, Test to Code and Property-Based Testing