2.2.1 · D3Design Principles

Worked examples — DRY — Don't Repeat Yourself

2,390 words11 min readBack to topic

The scenario matrix

Think of every DRY decision as a point on a grid. The two questions that decide everything are:

  1. Do the copies encode the same knowledge? (same rule / fact / structure)
  2. Would they change together, for the same reason?

Only when both are "yes" is it a true violation. Here is the full space of cells this topic can throw at you:

Cell Name Same knowledge? Change together? Verdict
A Real logic duplication yes yes DRY it (extract function)
B Real fact/data duplication yes yes DRY it (single source of truth)
C Structural duplication yes yes DRY it (loop / template)
D Coincidental duplication no no Leave apart
E Not-yet-a-pattern (2 copies) maybe unknown Wait (Rule of Three)
F Over-DRY god abstraction yes but diverging Undo — re-duplicate
G Degenerate: 1 copy () already DRY, do nothing
H Limiting: many copies, exam-style yes yes quantify the risk

The rest of the page is eight worked examples, one per cell A–H. Every symbol used (, , ) is exactly the one built in the parent's cost model — nothing new is smuggled in.

Figure — DRY — Don't Repeat Yourself

Look at the figure above: the grid axis "Same knowledge?" runs left→right, "Change together?" runs bottom→top. The amber region (top-right) is the only place you DRY. Everything else is a trap or a no-op.


Cell A — Real logic duplication

Forecast: Guess now — same knowledge? same reason to change? What's before and after?

  1. Identify the knowledge. The rule is "total = price + 18% of price". Why this step? DRY is about knowledge, so we must first name the knowledge, not stare at characters.
  2. Count the homes. That rule lives in two places → . Why this step? The parent's cost model needs .
  3. Ask the test. "Do both change together, for the same reason?" If the tax law changes, both must change. → Yes → true duplication, Cell A. Why this step? This separates A from D.
  4. Extract. Give the fact and the logic one home each:
    TAX_RATE = 0.18
    def with_tax(price):
        return price + price * TAX_RATE
    total_a = with_tax(price_a)
    total_b = with_tax(price_b)
    Why this step? Now : one edit changes the rule everywhere.

Verify: Cost model. Take (one unit per edit). Before: . After: . Halved. Numerically, with_tax(100) = 100 + 100*0.18 = 118, and the original 100 + 100*0.18 = 118 — identical output, so behaviour is preserved. See Refactoring — Extract Method.


Cell B — Real fact/data duplication (single source of truth)

Forecast: Guess the number of edits, and guess the consistency probability.

  1. Name the knowledge. "The set of states we accept." One fact. Why this step? Data facts are still knowledge — Cell B, not C.
  2. Count homes. Frontend + backend → . Why this step? Needed for both cost formulas.
  3. Apply consistency model. With slip probability per copy, the parent gives . Why this step? Quantifies the drift risk before fixing.
  4. DRY it. Store the list in one config/table; both layers read it → this is a Single Source of Truth. Now , so . Why this step? One authoritative home removes the drift entirely for future edits.

Verify: and . Editing 2 copies is 9 percentage points more likely to drift than editing 1. Adding "WA" under WET touches 2 files; under the fix, 1.


Cell C — Structural duplication (loop / template)

Forecast: Guess what varies and what stays fixed.

  1. Separate the constant from the variable. The procedure draw_button(...) is fixed; only the label varies. Why this step? DRY structure = express the fixed part once, feed it the varying data.
  2. Verdict. Same procedure, changes together (fix a bug in drawing → fixes all) → Cell C, a real violation. Why this step? Confirms it's not coincidental.
  3. Extract the loop.
    for label in ["Save", "Cancel", "Help"]:
        draw_button(label)
    Why this step? The drawing procedure now appears once; the data list carries the variation.

Verify: Count of draw_button calls executed is unchanged: 3 buttons drawn either way. Count of draw_button lines written dropped from 3 to 1. Behaviour identical, source-of-truth for "how to draw a button" is now singular.


Cell D — Coincidental duplication (the #1 trap)

Forecast: Same characters — but is it the same knowledge? Guess before reading.

  1. Name each knowledge. One is "max upload size"; the other is "rows per page." Different facts. Why this step? DRY is knowledge, not the digits 100.
  2. Apply the test. "Do they change together, for the same reason?" If we later allow 500 MB uploads, page size should not jump to 500. → No. Why this step? This is the exact test the parent's mnemonic gives.
  3. Verdict. Cell D — coincidental (accidental) duplication. Merging would couple two unrelated decisions. Leave them apart. Why this step? See The Wrong Abstraction and Single Responsibility Principle — "one reason to change".

Verify: Suppose we later set upload to 500 while page size stays 100. With the (wrong) merge SHARED_100, we cannot express two different values — the abstraction breaks. With two constants, both values are freely expressible. So keeping them separate strictly preserves capability the merge destroys.


Cell E — Not yet a pattern (Rule of Three)

Forecast: . Does two copies already justify an abstraction?

  1. Count occurrences. Two. Why this step? The Rule of Three triggers on the third.
  2. Judge confidence. With only 2 examples you can't yet see which parts are essential vs coincidental to the pattern — extracting now risks Premature Abstraction. Why this step? An abstraction built from 2 data points often fits neither future case.
  3. Decision. Wait. Tolerate the duplication; when the third occurrence appears, the true shape is clear, then extract. Why this step? Sandi Metz: "duplication is far cheaper than the wrong abstraction."

Verify: Risk arithmetic. With , two copies drift with probability . That 19% risk is real but cheaper than paying for a wrong abstraction you must later unwind (Cell F shows that unwind cost). The Rule of Three is a bet that seeing 3 examples beats guessing from 2.


Cell F — Over-DRY: undoing a god-abstraction

Forecast: More flags, or split it back apart? Guess the direction.

  1. Diagnose. The single function is drowning in parameters/branches — each new requirement adds a flag. Why this step? This is the signature of The Wrong Abstraction: the abstraction is fighting the code.
  2. Recall the rule. "Duplication is far cheaper than the wrong abstraction." The cases are diverging, so they are no longer the same knowledge. Why this step? Once knowledge diverges, the DRY justification evaporates.
  3. Inline and re-split. Copy the function body back into the call sites, delete the flags, and let each case be its own simple function. Why this step? You trade a little duplication for a lot of clarity — the healthy inverse of extraction.

Verify: Complexity count. A function with 3 boolean flags has possible branch combinations to reason about. Three plain functions have paths total. : re-duplicating cuts the reasoning surface. See KISS Principle.


Cell G — Degenerate case: only one copy ()

Forecast: What is here, and what does the cost model say?

  1. Count homes. . Why this step? is the whole story.
  2. Plug the model. (minimum possible) and (maximum possible). Why this step? These are the best-case values the parent derived.
  3. Verdict. Already DRY. Extracting further (splitting the one function for its own sake) would create abstraction with no duplication to justify it — YAGNI. Do nothing. Why this step? DRY is satisfied at ; going below is meaningless.

Verify: With : and . No arrangement of a single home can beat cost 1 or consistency . The reviewer's request is a no-op.


Cell H — Limiting / exam-style twist: many copies

Forecast: Guess whether 5 copies is "a bit worse" or "dramatically worse" than 1.

  1. Set up. . WET: . DRY: . Why this step? Identify the two scenarios to compare.
  2. Consistency, WET. . Why this step? Direct use of the parent's .
  3. Compute. — barely better than a coin flip! Why this step? Shows exponential decay is scary, not academic.
  4. Consistency, DRY. . Why this step? The single-source-of-truth baseline.
  5. Failure factor. WET failure . DRY failure . Ratio . Why this step? Turns probabilities into a headline: "~4× more likely to break."

Verify: ; ; failure ratio . Five copies are ~4× more likely to leave a stale timeout than one. This is the parent's exponential-decay claim made concrete.


Active Recall

Recall Which single cell of the matrix is the one where you

must leave code apart despite identical text? Cell D — coincidental (accidental) duplication: same characters, different knowledge, different reasons to change.

Recall In Cell H, why is 5 copies so much worse than 2, not just "a bit" worse?

Because decays exponentially: but . Each extra copy multiplies the survival chance by .

Recall Cell F says to

undo an abstraction. When is that correct? When the merged cases have diverged so they no longer encode the same knowledge, and the flags/branches now make the code harder than the duplication was — "the wrong abstraction."

Recall What is the verdict and reason for Cell G (

)? Already DRY. and are already optimal; further "DRYing" is abstraction with no duplication to justify it (YAGNI).



Connections

  • DRY — Don't Repeat Yourself — the parent principle these cases exercise.
  • Single Source of Truth — Cell B.
  • Rule of Three — Cell E.
  • The Wrong Abstraction / Premature Abstraction — Cells D and F.
  • Single Responsibility Principle — the "one reason to change" test.
  • Refactoring — Extract Method — the tool for Cells A and C.
  • KISS Principle / YAGNI — Cells F and G.
  • WET — Write Everything Twice — the "before" state in most cells.