الدرس 1 من 2
DRY Is About Knowledge, Not About Text
Two pieces of code that look identical today may encode two different rules. Deduplicating them couples two teams to one abstraction — often the most expensive mistake in a young codebase.
DRY stands for "Don't Repeat Yourself", and it is almost universally taught as "do not write the same code twice". The original formulation, from Andy Hunt and Dave Thomas, says something quite different: every piece of *knowledge* must have a single, unambiguous, authoritative representation within a system. Knowledge, not text. The gap between those two readings is responsible for a large share of the tangled code you will meet in your career.
| Two similar pieces of code | Must they change together? | Deduplicate? |
|---|---|---|
| VAT calculation in the invoice module and in the quote module | Yes — one tax rule, one legal source | Yes: extract a tax policy both call |
| Validation of a customer email and of a supplier email | Not necessarily — supplier onboarding may later require a corporate domain | No: leave both, revisit if the rules stay identical for a year |
| Two `formatMoney` helpers producing the same string | Yes — a currency display rule is one rule | Yes, and put it where both can see it |
| Retry loops in the payments client and the email client | No — different failure modes, different acceptable latency | No: a shared "retry helper" would force one policy on both |
// Year 1 — extracted because two call sites looked identical.
function sendNotification(user: User, message: string): Promise<void> { /* ... */ }
// Year 2 — marketing needs unsubscribe links, transactional must not have them.
function sendNotification(user: User, message: string, marketing: boolean): Promise<void>;
// Year 3 — the flags multiply, and every caller passes a different combination.
function sendNotification(
user: User,
message: string,
options: {
marketing?: boolean;
skipQuietHours?: boolean;
forceEmail?: boolean;
locale?: string;
retryOnBounce?: boolean;
},
): Promise<void>;
// The tell: no caller uses more than two flags, and no two callers use the
// same two. That is four different functions wearing one name.A workable rule for when to unify
- 1
Wait for the third occurrence
Two similar pieces of code are a coincidence often enough that acting on them is a coin flip. By the third you can see the shape of what actually varies, and the abstraction you extract will be the right shape rather than a guess.
- 2
Name the knowledge, not the code
If you can name the rule — "German VAT rate", "password strength policy", "order-number format" — it is knowledge and it wants one home. If the best name you can find is `processData` or `handleStuff`, you have found similar text, not shared knowledge.
- 3
Check who owns each copy
If the two copies belong to different teams or different bounded contexts, unifying them creates a cross-team dependency for the sake of removing a few lines. That trade is almost never worth it, and it is how a shared "common" library becomes a bottleneck.
- 4
Prefer duplication across boundaries, unification within them
Inside one module, unify aggressively — the cost of being wrong is a small refactor. Across module or service boundaries, tolerate duplication, because unification there is a coupling decision with an architectural cost of reversal.
In practice
The shared library that became a bottleneck
A company with six product teams noticed that each had its own address validation. An internal platform team consolidated them into a `@company/common` package and mandated its use, removing roughly 900 lines of duplication.
Constraints
- Six teams, six release cadences
- Address rules differ by product: shipping needs a deliverable address, billing does not
- The platform team has three engineers
- Every change to the package requires review by that team
Decision
After eighteen months the package was split: a small `address-format` library holding the genuinely shared postal-format knowledge, and product-specific validation moved back into each team's codebase.
Why
The postal format of a German address is one piece of knowledge with one authoritative source, so it belonged in a shared library. Whether an address is acceptable for a given product is a product decision that each team owns, and centralising it had turned a three-person team into a queue standing between six teams and their own business rules.
What it cost
The split reintroduced perhaps 300 lines of similar-looking validation code, and two teams did write slightly different error messages for the same case. In exchange, the median time to change a product's address rules went from eleven days to under one, and the platform team stopped spending half its capacity arbitrating other teams' requirements. The duplication was the cheaper of the two costs.
Seeing the same twenty lines in two services
As a developer
Feels the itch immediately and extracts a shared package, because repetition is the visible problem and removing it is satisfying and easy to justify in a review.
As an architect
Asks whether the two services must always agree about this rule. If they must — a shared wire format, a shared identifier scheme — the duplication is a correctness risk and unification is right. If they need not, the shared package converts an independent-deployment property that the split was meant to buy back into a coordinated release, which is a much larger loss than twenty lines.
Your order service and your reporting service both contain a function that maps order status codes to human-readable labels. Should you extract a shared package?RevealHide
Probably not, and the deciding question is who owns the labels. If the labels are part of a published contract that both must render identically — a customer sees the same word in the app and in a monthly report — that is one piece of knowledge and it wants one source, ideally served as data rather than as code. If reporting is free to phrase things for analysts while the order service phrases them for customers, they are two vocabularies that currently coincide, and a shared package would make every wording change a cross-service release. Notice that the answer comes from asking about ownership and about who reads the output, not from counting the duplicated lines.
Key takeaways
- DRY is about a single authoritative source for a piece of knowledge, not about eliminating similar-looking text.
- The deciding question is whether both copies must always change together for the same reason.
- A wrong abstraction decays into a parameter per divergence and is much harder to undo than duplication.
- Wait for the third occurrence before unifying; by then the varying parts are visible.
- Unify freely inside a module; tolerate duplication across team and service boundaries, where unification is a coupling decision.