What DRY, KISS and YAGNI mean
DRY (don't repeat yourself) means each piece of knowledge in a system should live in one place. KISS (keep it simple, stupid) means choosing the simplest design that works. YAGNI (you aren't gonna need it) means not building features or flexibility until a real requirement asks for them.
All three aim at code that is cheap to read and cheap to change. They sometimes pull in different directions, and most of the judgment lies in those conflicts.
DRY was named in The Pragmatic Programmer by Andrew Hunt and David Thomas (1999). YAGNI comes from Extreme Programming.
DRY is about knowledge, not similar-looking code
Hunt and Thomas define DRY as: every piece of knowledge must have a single, unambiguous, authoritative representation within a system. The key word is knowledge. A business rule, a limit, a schema or a mapping should have one source of truth.
Real duplication is the same fact stated twice. If the maximum username length is 32 in the signup form, 30 in the API validator and 32 in the database column, one of them is already wrong. Define the rule once and derive the others from it, for example by sharing one validation schema between client and server.
Code that looks the same can encode different knowledge. A VAT calculation and a sales commission might both multiply a price by 0.2 today. Merging them into one helper couples two rules that change for different reasons, so the next change to either one risks breaking the other.
Ask whether the two pieces of code would change together, for the same reason. If yes, the duplication is worth removing. If no, the resemblance is a coincidence and can stay.
KISS and YAGNI: build for the requirement you have
KISS favors the plainest design that meets the requirement: a loop over a clever one-liner, a function over a class hierarchy, the standard library over a new dependency. Simple code has fewer places for bugs to hide and is easier for the next reader to change.
YAGNI targets speculative work. A feature built for a guessed future takes time to build, adds code everyone must read and test, and is often wrong when the real requirement arrives. Building it later, with the facts in hand, is usually cheaper.
YAGNI has limits. Some decisions are expensive to change later: a public API contract, the shape of a table that will hold years of data, how you store passwords. Think those through up front, even if you build only part of them now.
Naming and small functions
Good names do more for readability than most other techniques. A name should say what a value means or what a function does in the reader's terms: overdueInvoices instead of list2, isEligibleForRefund instead of check.
Small functions help when each one does one job at one level of detail. A function that parses a request, applies three business rules and formats an email mixes levels. Extracting each step behind a clear name makes the top-level function read like a summary.
The trade-off is navigation. Splitting code into many three-line functions forces the reader to jump around to follow one idea. Extract a function when its name explains more than the code it replaces, and keep the code inline when it doesn't.
Comments still have a place. Use them to explain why the code does something surprising, such as a workaround for a library bug, rather than to restate what the code does.
The cost of the wrong abstraction
A wrong abstraction is shared code that forces unrelated cases into one shape. It usually starts as a reasonable DRY refactor of two similar functions. Then a third caller needs a small difference, so the function grows a flag, then another, and soon every change has to consider every caller.
Sandi Metz summarized the trade-off: duplication is far cheaper than the wrong abstraction. Duplicated code is easy to see and easy to change locally. A tangled shared function is hard to change without breaking a caller you forgot about.
The fix is to inline the shared code back into each caller, delete the parts each one doesn't use, and look again. Many developers wait for a third copy before extracting anything, because three cases show what varies and what stays the same.
Signs that shared code has become the wrong abstraction:
- Boolean or mode parameters that switch large blocks of behavior inside one function
- Callers that pass values only to skip parts of the shared logic
- A helper whose tests mostly cover special cases for one caller
- Changes to the shared code that need sign-off from several unrelated features