DRY, KISS, YAGNI and Other Heuristics
Learning Objectives
Apply DRY, KISS, YAGNI, separation of concerns, Law of Demeter, Tell-Don't-Ask and command-query separation.
Recognise when a heuristic is being over-applied.
Prerequisites: Module 19.
Intuition and Definition
DRY — Don't Repeat Yourself Every piece of *knowledge* has one authoritative place
Merging code that only *looks* alike but changes for different reasons KISS — Keep It Simple Prefer the simplest design that works Refusing necessary structure YAGNI — You Aren't Gonna Need It
Don't build for imagined future requirements Ignoring known, scheduled requirements Separation of concerns
Keep UI, business rules, persistence apart Layers for a 50-line tool Law of Demeter
Talk to immediate collaborators, not strangers (a.getB().getC().do()) Writing dozens of pass-through methods Tell, Don't Ask
Ask an object to do the work instead of pulling its data and deciding outside
Forcing behaviour into data holders that shouldn't know about it Command-Query Separation
A method either changes state (command) or returns data (query), not both
Banning pop()-style methods that are naturally both Composition over inheritance Prefer assembling behaviour from parts Never using inheritance where it fits Least privilege
Expose the minimum surface (access, permissions) Explicit dependencies
Pass collaborators in; avoid hidden globals These are heuristics, not laws. Each protects against a specific failure; apply it when that failure is likely.
Why It Matters
Most maintenance cost comes from duplicated knowledge, needless complexity and hidden coupling. These heuristics are quick review questions that catch them early.
Syntax and Language Notes
# Law of Demeter violation: the caller navigates the object graph
fee = order.customer.account.billing_plan.late_fee()
# Tell, don't ask: let the order answer
fee = order.late_fee()Complete Example
#include <iostream>
#include <string>
#include <vector>
// ASK style: the caller pulls data and makes the decision (rule duplicated in every caller)
struct TankAsk { double litres; double capacity; };
std::string askStyle(const TankAsk& t) {
return (t.litres / t.capacity < 0.1) ? "LOW FUEL" : "ok";
}
// TELL style: the object owns the rule; callers just ask for the result
class FuelTank {
double litres_;
double capacity_;
static constexpr double kLowFraction = 0.1; // DRY: the threshold lives in one place
public:
FuelTank(double l, double c) : litres_(l), capacity_(c) {}
bool isLow() const { return litres_ / capacity_ < kLowFraction; } // query
void refuel(double l) { litres_ = std::min(capacity_, litres_ + l); } // command
};
// Law of Demeter: Car exposes what callers need instead of its parts
class Car {
FuelTank tank_{4.0, 50.0};
public:
bool needsFuel() const { return tank_.isLow(); } // no car.tank().isLow() chains
void refuel(double l) { tank_.refuel(l); }
};
int main() {
std::cout << "ask: " << askStyle({4.0, 50.0}) << "\n";
Car car;
std::cout << "tell: needs fuel? " << car.needsFuel() << "\n";
…ask: LOW FUEL
tell: needs fuel? 1
tell: after refuel needs fuel? 0Step-by-Step Walkthrough
In the ask style, the "10% is low" rule lives in a free function; every UI or logger that needs it will copy it.
In the tell style, FuelTank::isLow() owns the rule (DRY) and is a pure query; refuel() is a command that returns nothing (CQS).
Car::needsFuel() hides the tank (Demeter): callers don't navigate car.tank().isLow(), so the tank can be redesigned freely.
Common Mistakes
DRY by coincidence: two functions with identical code today but different owners (pricing vs. tax) — merging them couples changes that should be independent. DRY is about *knowledge*, not text.
YAGNI ignored: generic plug-in frameworks for one implementation.
KISS misread as "no abstractions": a 400-line function is not simple, just unstructured.
Demeter taken literally: fluent builders and value-object chains (date.plusDays(1).atNoon()) are fine — they return new values, not strangers' internals.
Trade-offs and Alternatives
Heuristics conflict: DRY can increase coupling, Demeter can add wrapper methods, YAGNI can delay a needed abstraction until it is harder to add. Resolve conflicts by asking what is *likely to change* and who owns that change.
Real-World Use
Code review checklists in most engineering teams include "duplicated logic?", "can this be simpler?", "do we need this now?", "is the caller reaching through objects?".
Practice
Q1. (Easy) What does CQS forbid?
Answer: A method that both changes state and returns information about it (except for deliberate exceptions like pop).
Q2. (Medium) invoice.getCustomer().getAddress().getCountry().getVatRate() — what heuristic is violated and how would you fix it?
Answer: Law of Demeter (and Tell-Don't-Ask). Give Invoice a method like vatRate() that delegates internally, or pass the VAT policy to the invoice.
Q3. (Advanced) Two modules contain the same 10-line date-formatting function: one formats invoice dates (legal requirements), one formats chart axis labels (UX). Should you merge them?
Answer: Probably not. They encode different knowledge with different reasons to change (legal format vs. visual design). Merging them would make a UX change risk legal compliance. Keep them separate, or share a low-level primitive (e.g. formatIso(date)) while each module keeps its own policy.
Interview Question
Q1. "DRY and YAGNI sometimes conflict. Give an example and how you decide."
Answer: You see a second similar handler and want to extract a generic framework (DRY), but there's no third case on the horizon (YAGNI). A common rule is the "rule of three": tolerate duplication at two occurrences, extract at three, and only extract *knowledge* that truly changes together. Decide by the cost of a wrong abstraction (hard to undo) versus the cost of duplication (two places to update).
Quick Quiz
Q1. DRY is about duplicated…?
Answer: Knowledge (decisions, rules) — not merely similar-looking code.
Q2. What does YAGNI stand for?
Answer: You Aren't Gonna Need It.
Q3. "Tell, don't ask" moves logic toward…?
Answer: The object that owns the data.
Summary
Heuristics are review questions, not rules: DRY (knowledge), KISS, YAGNI, SoC, Demeter, Tell-Don't-Ask, CQS, composition over inheritance.
When they conflict, optimise for what is likely to change.
Related: Modules 19, 21, 52 (code smells).