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:
- Do they encode the same fact?
- 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:
- Spot the hidden fact.
- Give it an address.
- 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.