A Pragmatic Take on the DRY(Don’t Repeat Yourself) Principle

“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”

“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”
The Pragmatic Programmer

Why “Don’t Repeat Yourself” Is About Ideas, Not Lines

Most developers first meet DRY when a senior dev points at two identical for-loops and mutters, We should refactor.

That narrow reading duplicate code is evil, misses the far bigger problem:

Bugs multiply when the same business fact is scattered around the system.

A VAT rate copied into three classes stops being a single truth; the moment it changes, two classes are already outdated. DRY is really a knowledge-management rule:

Every piece of domain knowledge must have one authoritative home.

The real goal is to keep shared knowledge: business rules, domain concepts, invariants, configuration in one place so there is only one way to change it.

To decide whether two bits of code violate DRY, ask:

  1. Do they encode the same fact?
  2. Will those facts always change together?

If the answer to both is yes, find a single source of truth.

If the answer to either is no, leave them alone; they are coincidentally similar, not duplicated knowledge.

The rest of the post shows how to recognise those facts, choose the right address, and avoid the subtle trap of merging code that only looks the same.

The Anatomy of a DRY Violation

Before we jump into code, here’s a simple mental checklist that sits above language specifics:

  • Identify the fact

Is it a constant, a rule, an invariant, or a business decision?

  • Test for co-evolution

Will every duplicate always change in lock-step?

  • Choose an ownership pattern

Constant -> enum or config value

Rule -> policy class or pure function

Invariant -> value object or schema constraint

  • Replace copies with references

Depend on the owner, don’t own the fact yourself.

Keep this four-step loop in mind as you read the examples, you’ll see it repeated verbatim.

Scattered Constants

Fact type: domain constant (VAT rate)

// OrderService.java 
double VAT = 0.20; 
 
// InvoiceService.java 
double VAT = 0.20;

Analysis

Fact? Standard VAT is 20 %.

Co-evolution? Yes, finance changes it system-wide.

Owner? A dedicated policy class.

// TaxPolicy.java 
public final class TaxPolicy { 
    public static final BigDecimal VAT_RATE = new BigDecimal("0.22"); 
}

Every module now references TaxPolicy.VAT_RATE, achieving DRY at the knowledge level.

Reused Validation Rules

Fact type: domain rule (what a valid e-mail looks like)

// userForm.tsx 
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) … 
 
// userService.ts 
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(request.email)) …

Analysis

Fact? Valid e-mail pattern.

Co-evolution? Definitely, reg-ex changes apply everywhere.

Owner? Shared utility module.

// email.ts 
export const EMAIL_REGEX = /^[^@\s]+@[^@\s]+\.[^@\s]+$/; 
export const isEmail = (v: string) => EMAIL_REGEX.test(v);

UI and backend share the same source of truth instead of diverging reg-exes.

Coincidental Similarity (Not a Violation!)

// purge.go 
for _, o := range orders { if o.IsExpired() { archive(o) } } 
 
// billing.go 
for _, o := range orders { if o.IsExpired() { chargeLateFee(o) } }

Fact? Two different business actions triggered by the same predicate.

Co-evolution? No — fees may be dropped while archiving remains.

Verdict: Leave them alone; merging would entangle unrelated rules and create false coupling.

Conclusion

The real DRY isn’t a stylistic clean-up; it’s a strategy for truth management:

  1. Spot the hidden fact.
  2. Give it an address.
  3. Make everyone else ring that doorbell.

Do this consistently and your codebase becomes a journal of explicit decisions. Each decision written exactly once and easy to revise.

Read more