DRY / KISS / YAGNI
DRY (Do not Repeat Yourself) is about ONE source of truth, not ZERO duplication. When to extract, when to leave well alone.
Design patterns — DRY
EXAMPLE
// ===== The principle =====
// 'Every piece of knowledge must have a single, unambiguous, authoritative
// representation within a system.' — Hunt & Thomas, The Pragmatic Programmer
// ===== What DRY is NOT =====
// - 'No duplicated lines of code'
// - 'Extract every two-line repetition into a function'
// - 'Always reuse a class across modules'
// The rule is about KNOWLEDGE, not LINES.
// ===== When to apply DRY =====
// 1. Same business rule expressed twice
// -> extract into a single, named function
// -> the function becomes the canonical place to change the rule
// 2. Same configuration value embedded in many files
// -> central constants module
// 3. Same algorithm with minor variations
// -> parametrise; pass the variation in
// 4. Same SQL query in two paths
// -> single repository function
// ===== When NOT to apply DRY (premature abstraction) =====
// 1. Two code paths LOOK similar but evolve differently
// -> coincidental duplication; leave them
//
// 2. Three lines repeated in a small file
// -> probably not worth extracting yet
//
// 3. UI markup that differs in tone or layout
// -> a shared component may bend out of shape
//
// 4. Test setup that reads cleaner in line
// -> arrange/act/assert reads better repeated
// Rule of thumb: 'Wait until you have 3 instances + a real change driver
// before you extract.' (the Rule of Three)
// ===== Example: applying DRY =====
// Before:
function totalUsd(order) {
const subtotal = order.lines.reduce((s, l) => s + l.qty * l.price, 0);
return Math.round(subtotal * 1.1 * 100) / 100; // 10% tax + 2dp
}
function totalGbp(order) {
const subtotal = order.lines.reduce((s, l) => s + l.qty * l.price, 0);
return Math.round(subtotal * 1.2 * 100) / 100; // 20% tax + 2dp
}
// After — one source of truth for line summation, parametrised tax:
function subtotal(order) {
return order.lines.reduce((s, l) => s + l.qty * l.price, 0);
}
function total(order, taxRate) {
return Math.round(subtotal(order) * (1 + taxRate) * 100) / 100;
}
// ===== Example: where DRY hurts =====
// Two error messages that LOOK similar:
// throw new Error("Order not found: ${id}")
// throw new Error("User not found: ${id}")
// Extracting notFound(kind, id) hides the call site and makes the messages
// awkward to evolve independently. Leave them.
// ===== When DRY conflicts with SRP =====
// Sometimes following DRY pulls unrelated concerns into a 'utility' class.
// Prefer SRP: one reason to change > one fewer line of code.
// ===== Patterns to internalise =====
// - DRY the KNOWLEDGE, not the LINES
// - Rule of three before extracting
// - Keep extracted helpers cohesive (SRP)
// - Name extracted units after the DOMAIN concept, not the mechanic
// ===== Pitfalls =====
// - Premature abstraction (shared bases that fork later)
// - 'Utility' classes that grow into a graveyard
// - Compressing two-line snippets that read fine in context
// - Treating DRY as a code-review hammer instead of a guideline
Why it matters
DRY is about one source of truth for each piece of business knowledge. Wait for the rule of three plus a real change driver before extracting. Compressing coincidental duplication causes more pain than it removes; keep the lens on knowledge, not line count.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// DRY — Don't Repeat Yourself. // KISS — Keep It Simple. // YAGNI — You Aren't Gonna Need It (don't build for hypotheticals).Try it Yourself »
Discussion
Loading…