50%

الدرس 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.

قراءة 10 دقيقة

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 codeMust they change together?Deduplicate?
VAT calculation in the invoice module and in the quote moduleYes — one tax rule, one legal sourceYes: extract a tax policy both call
Validation of a customer email and of a supplier emailNot necessarily — supplier onboarding may later require a corporate domainNo: leave both, revisit if the rules stay identical for a year
Two `formatMoney` helpers producing the same stringYes — a currency display rule is one ruleYes, and put it where both can see it
Retry loops in the payments client and the email clientNo — different failure modes, different acceptable latencyNo: a shared "retry helper" would force one policy on both
Identical-looking code, opposite conclusions. Only the middle column decides.
The characteristic decay: a shared abstraction acquiring one parameter per divergence.typescript
// 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. 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. 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. 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. 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?Reveal

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.