Lesson 0028 · Master Designer · Module 1
Abstraction vs Duplication
DRY (lesson 0002) says don't repeat knowledge. The Master's version: duplication is often the cheaper rent — the wrong abstraction is a mortgage you pay forever.
Mission tie-in: the second Complexity Trade-Off — where the principles of Phase I become something you balance instead of obey.
Knowledge: two kinds of rent
Duplication costs you on every change — edit N copies. Abstraction costs you on every read — everyone must understand the shape, and if the shape is wrong, every consumer pays. The trade is between these two rents.
Why the wrong abstraction is so expensive:
# the abstraction that guessed wrong
class FeeCalculator: # "all fees are one thing" — wrong
RATE = Decimal("0.01")
@classmethod
def fee(cls, amount: Money, kind: str) -> Money:
if kind == "bank":
return amount * cls.RATE
if kind == "wire":
return amount * cls.RATE
raise ValueError(kind)
# now wire fees change (clearing house), and the abstraction must be bent:
def fee(self, amount, kind):
if kind == "wire":
return amount * Decimal("0.02") # a branch, because one RATE was wrong
The two fees were never one knowledge (lesson 0002's false DRY) — the abstraction forced them into one shape, and every future divergence has to fight the shape. The duplicated version would have been two obvious functions, each change contained.
Sandi Metz's rule: duplication is far cheaper than the wrong abstraction. And the corollary — the abstraction isn't wrong if it was earned: three occurrences with the same change pattern, then extract. The rule of three is a rent calculation: by the third occurrence, the duplicated copies' change-rent exceeds the abstraction's read-rent.
Pricing the two rents
Three copies of a validation routine, changed four times a year. Duplication rent: 3 edits per change × 4 changes = 12 edits a year, plus the cost of the copy someone forgets (roughly once a year, found in production).
Abstraction rent: 1 edit per change — if the three always change together. If they change independently, the abstraction converts three independent edits into one negotiated edit, and each divergence adds a branch that all three consumers must then read past.
So the deciding number is not "how many copies" but how often the copies change together. Three copies that always move as one are an abstraction waiting to be extracted. Three copies that move on separate schedules are three facts, and merging them buys 12 cheap edits at the price of every future divergence.
| You see this | What it costs | The move |
|---|---|---|
| a parameter named kind / type / mode | The abstraction is asking callers which one it really is. | Two functions with two honest names. |
| if isinstance(self, …) inside a base class | The shape was wrong and is now being bent from inside. | Split the hierarchy along the real difference. |
| a helper with six optional parameters | One function serving six callers, none of them well. | Inline it back; re-extract what actually repeats. |
| three copies that changed together, twice | The shared shape is evidence now, not a guess. | Extract it — this is what earned looks like. |
Skill: whose rent is cheaper?
Two fees look identical today but are set by different contracts. Extract?
The rule of three says extract when:
The worst position to be in is:
Practice on your own code
Find an abstraction in your codebase that was extracted before its time (a helper with a kind/type flag, a base class with branchy subclasses). Ask: was it earned by three occurrences with one change pattern? If not, sketch the duplication it should have been.
Reveal: a wrong-abstraction rescue
A Notifier base class with send() overridden by Email and SMS — extracted at two occurrences. When a third channel arrived with a different retry semantics, the base class's shape had to bend. Rescue: delete the base, keep two functions (lesson 0016's callables), let each channel evolve alone until a real shared step appears three times.
Your win
You can compute the two rents — change-rent of duplication vs read-rent of abstraction — and you know the asymmetry that makes wrong abstractions the expensive mistake.
Read and watch deeper
- "The Wrong Abstraction", Sandi Metz — the essay this lesson is built on.
- The Pragmatic Programmer, Hunt & Thomas — the DRY chapter, read now with its limits visible.
- A Philosophy of Software Design, Ousterhout — ch. 6 "General-Purpose Modules Are Deeper": what an abstraction must earn to be deep.
- Watch: ArjanCodes YouTube — search "DRY" or "duplication".
- Next: lesson 0029 — Configuration vs Convention.
- Reference: Trade-offs — abstraction vs duplication; glossary — false DRY, rule of three.
Bring a flag-driven helper to your agent-teacher and decide whether it's an earned abstraction.