This is the whole thing: the legacy calculator, six behaviours pinned by running it, a clean refactor, and the equivalence table proving the output stayed byte-for-byte identical. The exercise is this same page with three wrong pins and an empty refactor — you supply both.

A characterization test — a check that captures what the code actually does — does not argue with the code, it records it. Each recorded value becomes a spec the code must keep meeting. Below, every pinned value was produced by running LegacyTotal; only then was the refactor (tidying the code without changing its behaviour) allowed to start, and the pins are what prove it changed nothing.

The battery — what the legacy code actually prints

Basket Gross Legacy prints Why it surprises
(empty) 0 0.00 the freebie
1 × 800 800 800.00 under every threshold
1 × 1000 1000 1,000.00 > is strict — exactly 1000 gets nothing
Laptop + 3 mice 1275 1,211.25 5% once: 1275 × 0.95
2 × 1050 2100 1,995.00 the trap — the 2% test reads the already-discounted total (1995), not the gross (2100), so it never fires
3 × 1000 3000 2,793.00 both fire sequentially: ×0.95 then ×0.98 — not a flat 7% (that would be 2790)

The fifth row is the whole lesson. Read the legacy code top to bottom and the second if looks like "orders over 2000 get another 2%". Run it and you learn the truth: it reads x after the first discount already fired. Nobody decided that — it fell out of reusing one variable. It ships today, so the refactor must keep it.

The refactor, live and identical

Same behaviour, said clearly: named constants, one method, and a comment on the one line where the surprise lives. Press Run — six rows, three columns in agreement, all ✓.

Take the legacy basket-total calculator with its two sequential discount thresholds, pin its six characterized behaviours — including the trap where the 2% test reads the already-discounted total — then refactor it into named constants and one clear method without changing any of them. Run the battery showing pinned, legacy, and refactored values byte-identical on every row.

Notice what the refactor did not do: it did not "fix" the strict >, did not make the discounts additive, did not re-read the gross for the second threshold. Every one of those would be a rewrite — a behaviour change smuggled in under the word refactoring — and the battery would have caught it with a ✗ before any customer did.


← Back to the exercise · Module 1 — Introduction · All solutions

Reconnecting…
The connection to the server was interrupted. Trying to restore it…
Trying again…
The connection could not be restored. Reloading the page…
The server was updated. Reloading the page to pick up the latest version.