Craft of code
Duplication vs the wrong abstraction
DRY is about knowledge, not text. Prefer duplication over a shared helper you cannot name. YAGNI for imaginary second use cases.
DRY (Don't Repeat Yourself) is advice about knowledge, not about text. Two blocks that look similar but change for different reasons should stay separate. Sandi Metz's line is the corrective: prefer duplication over the wrong abstraction.
YAGNI (You Aren't Gonna Need It) is the sibling: do not build the plugin system, the generic BaseService, or the config-driven workflow engine for a second use case you are imagining. Design for today's requirements with seams that allow tomorrow's change — deep interfaces and clear modules — without implementing tomorrow's features.
The big idea
Trade-off
Extract shared helper now vs wait. Extract when three call sites share the same rule and you can name the rule precisely (calculateProration). Do not extract when two call sites merely share a shape (map + filter that will diverge next sprint). Premature shared helpers become god-functions that every feature must bend around.
The wrong abstraction increases change amplification: every new caller must twist the helper, and every twist surprises the old callers.
Walking through a decision
Two checkout paths both map line items and filter zeros. Next sprint, EU needs tax-inclusive display and US does not. If you extracted normalizeLines last week, you now add a flag, then another, then a region argument. The helper is a god-function. Leaving the two loops duplicated for a week would have been cheaper.
Three refund paths all compute "amount already captured minus amount already refunded, never below zero." You can name that: refundableCents. Extract it. The rule is the same knowledge in three places.
Promotion evidence looks like: fewer production surprises in your modules, reviewers commenting that your diffs are easy to follow, and juniors able to extend your code without a pairing session every time. Saying "I'd make it clean" in an interview is weak; naming change amplification and information hiding is strong. Walk a before/after with concrete costs ("a one-line product change took three files; after extract, one").
Common mistakes
- Extracting on the second similar
map. Wait until you can name the rule. - Building
BaseServicefor an imaginary third caller. - Treating similar text as the same knowledge.
Check yourself
Two functions share a map + filter that you expect to diverge next sprint. Extract now?
No. Duplicate until the rule has a name and a third real caller.
Three paths all compute captured-minus-refunded, never below zero. Extract?
Yes. That is one named rule: refundableCents.
Key takeaways
- DRY is about knowledge, not text.
- Prefer duplication over a helper you cannot name.
- Extract at three call sites with one precise name, not two call sites with a similar shape.
- YAGNI: seams for tomorrow, features for today.
Further learning
Next, the part you cannot change cheaply once callers exist: interfaces that age well.
Related
Writing code others can maintain
Managing complexity with deep modules, clear naming, honest comments, consistent style, and error strategies that shrink the blast radius of change.
Deep modules
A deep module hides a lot of complexity behind a small interface. Shallow pass-through layers add names without adding leverage.
Software complexity
Complexity shows up as change amplification, cognitive load and unknown unknowns. The next stranger's change is the cost that matters.