Case Study: A Change Done Well
This is the whole group applied to one change. The task is small and real: add a discount code to the Storefront checkout. We’ll do it twice. First the way an under-thought change goes, then the way an engineer does it, through simple, correct, and fitting.
It’s the same Storefront the Product group decided to bet on, one-tap checkout, now at the code level. Strategy sized the leak, Product chose the bet, and this is what it looks like to build a piece of it well. Read it as the model for how a change should land.
The Task#
The definition says: shoppers can enter a code at checkout; a valid code takes a percentage off the total; the discounted total shows on the cart, the checkout, and the confirmation email. That’s it. One flat percentage per code.
Version 1: Shotgun Surgery#
The change “works” in the demo. Here’s what actually shipped:
// cart.tsx total = sum(items) * (1 - pct) // checkout.tsx total = sum(items) * (1 - pct) // email.ts total = sum(items) * (1 - pct) // admin.tsx total = sum(items) * (1 - pct) // + a DiscountRulesEngine with tiers & stacking, for one flat coupon
Count the failures of judgment:
- Not simple. A configurable rules engine, tiers, stacking, schedules, was built for a feature that is one flat percentage. Hundreds of lines nobody needs, that everybody now maintains.
- Not correct. No guard on a code over 100% (the charge goes negative), no handling of an expired or unknown code, no decision on rounding. The happy path was the only path.
- Doesn’t fit. The discount math was pasted into four files. The truth now lives in four places that will drift. The change touched nine files; the reviewer can’t tell feature from collateral.
It passed the demo. It will generate a support ticket within the week (a shopper charged a different amount than the email showed) and slow every pricing change for a year.
Version 2: The Engineer’s Change#
Understand first#
Before touching anything, trace how a total is computed today. It turns out the cart, checkout, and email each compute it themselves, the truth is already duplicated three times. That’s the real find: the task isn’t just “add a discount,” it’s “add a discount to a calculation that has no single home.” An engineer fixes the home first, then the discount is trivial. Before consolidating, pin the current behavior with a quick test, feed a few carts through all three copies and record what they produce today, so that when you collapse them into one home you can prove the number didn’t move.
Fit: give pricing one home#
Two jobs get separated. The pricing rule goes in one pure function, so the math lives in exactly one place. Validating the code, unknown or expired, and telling the shopper why, is a different job that belongs at the boundary where the code is entered, not buried in the math.
// pricing.ts — the rule lives in ONE place (a pure function)
export function computeTotal(cart, discountPct = 0): Money {
const subtotal = sum(cart.items)
const discounted = Math.max(0, subtotal * (1 - discountPct)) // never negative
return roundMoney(discounted + cart.tax + cart.shipping) // rounded once
}
// codes.ts — validation is a SEPARATE job, at the boundary:
function validateCode(code) {
const rec = CODES[code]
if (!rec) return { rejected: "unknown" } // → tell the shopper
if (rec.expired) return { rejected: "expired" } // expiry is representable
return { pct: rec.pct }
}
// returns { pct }, or { rejected: "unknown" | "expired" }
(Money here is a currency-safe value, integer cents, not a raw float, because floats and money don’t mix.)
One more distinction that trips people up: cart and checkout call computeTotal to display a price, but the amount actually charged is captured once, at purchase, and stored on the order, and the confirmation email reads that stored total, not a fresh recompute. That’s the real fix for “charged one price, emailed another”: not three surfaces re-running a function and hoping they agree, but one authoritative value, computed once and persisted. A shared pure function gives the rule one home; a persisted order total gives the amount one source of truth. Serious money code wants both.
Simple: build for the problem you have#
One flat percentage per code is a lookup in codes.ts, not an engine. No tiers, no stacking, no schedules, because none exist yet. The day a real second case appears (a tiered code), it’s added then, to the one place pricing lives. The simple version isn’t the lazy version; it’s the one that’s trivial to extend on the day extension is actually needed.
Correct: handle what breaks it#
Walk the edge checklist and decide each one on purpose, including the ones this function doesn’t own (deciding “not here, and where instead” is still a decision):
| Edge | Decision, pinned by a test |
|---|---|
| No code entered | Full price; not an error |
| Unknown / expired code | No discount, and tell the shopper why |
| Code over 100% | Clamp to zero; never a negative charge |
| Rounding | Round once, in computeTotal, so every surface agrees |
| Empty cart | Total is zero; code is a no-op |
| Payment call fails / times out | Not this function's job; the order/payment boundary owns it, no order is created on failure, and it's safe to retry |
| Two shoppers, last unit in stock | Not this function's job; stock is decremented atomically at checkout, one wins, one is told it sold out |
Each decision is a product call as much as a technical one, and each becomes a test, so the next person can’t quietly undo it.
The Difference, in the Diff#
| Shotgun surgery | The engineer's change | |
|---|---|---|
| Files touched | 9 across 5 modules | pricing + 3 call sites |
| Where price lives | 4 drifting copies | 1 source of truth |
| Next pricing change | Hunt 4 places, miss 1 | One edit, trickles down |
| Edges | Negative charges in prod | Decided and tested |
| Built | A rules engine for one coupon | A lookup; extend when real |
Same task, same afternoon, same feature shipped. One version leaves four copies of the truth, negative charges waiting to happen, and an engine nobody needs. The other leaves a system where the pricing rule has a home, the charged amount has a single authoritative value, and the edges are decided and tested. Both pass the demo; only one is safe to build on.
The Bar#
When you’re handed a change, this is the shape of a good one: you understood the system before you touched it, you built the smallest thing that solved the real task, you handled what breaks it (including the parts you push to a boundary), and you changed the concept in one place so it ripples cleanly. The diff is small, local, and obvious, and the person who reviews it can see exactly what the feature is. That’s what the three questions from Thinking Like an Engineer buy you: a change you can stand behind, not just one that runs.
Back to: Thinking Like an Engineer · Related: Define, How We Ship, Testing & TDD