iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…