A Pragmatic Approach

9 min read

Core idea

Every design principle is a special case of one rule

Thomas and Hunt's most striking claim in this topic is small and easy to miss. After two decades of teaching design principles — decoupling, single responsibility, naming, modularity, low-coupling-high-cohesion, all of it — they decided they had been overcomplicating things. The whole pile reduces to a single value: ETC — Easier To Change. Why is decoupling good? It makes things easier to change. Why is single-responsibility useful? Change shows up in one module instead of seven. Why is naming important? You have to read code to change it. Every principle in this topic, and arguably every principle of software design ever written down, is a special case of ETC.

Eight techniques for making change cheap

The topic's eight topics are not arbitrary — they are a catalog of techniques that all serve the ETC value. DRY keeps knowledge in one place so a change to that knowledge changes one location. Orthogonality keeps that knowledge cleanly separated so a change to one concern doesn't ripple through others. Reversibility keeps your most expensive decisions undoable. Tracer Bullets let you change direction mid-flight by giving you a working end-to-end system early. Prototypes let you cheaply discover what the right thing to build is before committing. Domain Languages let you change behavior at the level of business intent, not implementation detail. Estimating lets you set expectations honestly so changes to scope, schedule, or staffing happen as conversations rather than crises.

Why it matters

Code that resists change is code that will be thrown away

The single most expensive thing in software is change you didn't plan for. Requirements shift, libraries deprecate, regulations land, scale arrives, an unexpected customer asks for something nobody anticipated. Code that can absorb these changes survives; code that can't gets rewritten or worked around with kludges. The ETC value is not abstract — it is the difference between a system that lives ten years and one that lives ten months.

Most of the techniques cost nothing up front

A pleasant surprise of this topic is that almost none of these techniques require extra effort. Naming a thing well costs the same as naming it badly. Putting knowledge in one place is cheaper than putting it in three. Keeping a decision reversible often costs less than committing to it. Estimating in honest units is faster than fudging. The ETC discipline is largely a matter of taste and habit, not budget — which means the only thing stopping a team from adopting it is conscious attention.

Key takeaways

The topics in this topic

Topic 8 — The Essence of Good Design

The topic opens with the manifesto. After cataloging dozens of design principles over the years, Thomas and Hunt collapsed them all to Easier To Change. ETC is presented as a value, not a rule — it lives in the background of your thinking the way other values do (honesty, generosity), nudging your micro-decisions without ever needing to be cited explicitly.

Tip 14: Good Design Is Easier to Change Than Bad Design.

The practical advice is a habit: for a week, every time you save a file, ask yourself did the thing I just did make the system easier or harder to change? It is a deliberate-practice drill, not an architectural framework. After a week, the question runs in the background. After a month, it shapes your code without you noticing.

Topic 9 — DRY — Don't Repeat Yourself

DRY is the most-quoted and most-misread principle in the book. The original 1999 phrasing led many readers to take it as "don't copy-paste lines of source." The 20th Anniversary Edition rewrites it to fix that. DRY is about knowledge: every piece of knowledge in the system must have one single, unambiguous, authoritative representation.

Tip 15: DRY — Don't Repeat Yourself.

The acid test the authors propose: when one fact about the system has to change, do you find yourself changing it in multiple places, possibly in multiple different formats — code and docs and database schema and config? Then your code is not DRY, even if no two lines are byte-for-byte identical. Conversely, two functions with identical bodies may both legitimately exist if they represent two different pieces of knowledge that just happen to share an implementation. The check is do they need to change together?

The topic walks through DRY violations across code, documentation, data representations, and even developer practices (two developers solving the same problem twice without knowing). The takeaway: hunt duplicated knowledge, not duplicated characters.

Topic 10 — Orthogonality

A geometry term repurposed for software. Two vectors are orthogonal if they are perpendicular — a change in one has zero effect on the other. Two software components are orthogonal if a change to one produces zero changes in the other. The whole concept of modules, layers, interfaces, separation of concerns, single-responsibility, and low coupling is captured by this one geometric image.

Tip 16: Eliminate Effects Between Unrelated Things.

Orthogonal systems are dramatically easier to test (each component tests in isolation), reuse (each component is self-sufficient), and modify (a change cascades through one component, not eight). The everyday signal that orthogonality is missing: you fix a bug in module A and module B mysteriously breaks. That ripple is the inverse of orthogonality, and every occurrence of it is a debt to be paid back later.

DRY and Orthogonality are presented as a pair. DRY says one piece of knowledge in one place. Orthogonality says one place affects only what it should affect. Together they describe a system where every change is small and local.

Topic 11 — Reversibility

Most architectural disasters trace back to one or two early decisions treated as immutable. "We're a Postgres shop." "We're SOAP all the way down." "We deploy on-prem." Three years later the choice is wrong but it has metastasized through the code, and replacing it is a year-long project. Thomas and Hunt's argument is that almost none of these decisions had to be irreversible — they became irreversible because nobody preserved the option to back out.

Tip 17: There Are No Final Decisions.

Tip 18: Forgo Following Fads.

Reversibility is the discipline of asking, on every major commit: what would it take to undo this? If the answer is "almost nothing," the decision is cheap and you can move fast. If the answer is "rewrite half the codebase," the decision is structural and deserves more thought — and often deserves a layer of abstraction that makes it reversible. The cost of the abstraction is almost always lower than the cost of the future migration.

Topic 12 — Tracer Bullets

A military metaphor. Tracer rounds are bullets that glow as they travel, letting the gunner see where they actually land and adjust aim in real time. Tracer-bullet development is the same principle: build an end-to-end working system as the first thing you do — even if every layer is hollow — so you can see how the pieces actually fit and adjust direction immediately based on real feedback.

Tip 19: Use Tracer Bullets to Find the Target.

This is the opposite of the traditional sequence (gather all requirements → design the full system → implement bottom-up → integrate at the end → discover the misalignments six months in). With tracer bullets, you do a thin slice through every layer first: a UI that calls an API that talks to a database that returns one hard-coded record. It works, end to end, on day three. Then you iterate, layer by layer, with users seeing progress continuously.

Tracer bullets differ from prototypes — they become the actual production code. You do not throw them away; you fill them in. The point is the trajectory, not the lethality.

Topic 13 — Prototypes and Post-it Notes

Where tracer bullets are kept, prototypes are designed to be thrown away. A prototype's job is to learn something: whether an architecture is feasible, whether an algorithm is fast enough, whether a UI flow is intuitive, whether an API is usable. Once the question is answered, the prototype's job is done — and the worst thing you can do is ship it.

Tip 20: Prototype to Learn.

The authors push hard on the disposability of prototypes. Build them in throwaway materials: a sketch on a Post-it, a no-error-handling hack in a scripting language, a UI made of static images. Strip out everything not required to answer the question. If a prototype looks too polished, stakeholders will inevitably ask "great, when can we ship it?" — and you will be pressured to harden a structure that was never built to be hardened. Visible scaffolding is a feature.

Topic 14 — Domain Languages

The closer your program text reads to the language of the domain it serves, the easier it is to change. A scheduling system whose code talks about "appointments," "slots," and "blackout periods" is easier to modify than one whose code talks about rows, IDs, and timestamps. A pricing engine that expresses business rules as something like discount when customer is platinum and basket over 100 is easier to change than one where the same logic is spread across imperatively-written if statements.

Tip 21: Program Close to the Problem Domain.

Domain languages can be internal (a library of well-named functions that read like prose in the host language) or external (a small DSL with its own parser). Internal DSLs are cheaper and almost always the right starting point — Ruby, Elixir, and Kotlin make them especially natural. External DSLs pay off when business users themselves need to read or write the rules. Either way, the goal is to lift the level of discourse so that small business changes produce small code changes.

Topic 15 — Estimating

The topic closes with the most unloved topic in software. Estimates are inevitable — somebody is going to commit to a date — but most developers either refuse to give them or give them dishonestly. Thomas and Hunt's pragmatic approach: estimate in honest units (days, weeks, months — not "two sprints," which is vague), state your assumptions explicitly, give a range rather than a number, and always iterate the estimate as you learn.

Tip 22: Estimate to Avoid Surprises.

Tip 23: Iterate the Schedule with the Code.

The single most useful tactic the authors recommend: when someone asks "how long will this take?", the correct first answer is "I'll get back to you." Then you go think about it, decompose the work, ask the obvious questions, and come back with a calibrated answer. The instinct to give an off-the-cuff number is what produces the estimates everyone laughs at later. Estimation is a skill of research, not of speech.

A useful sanity check on your own estimating skill: after each project, compare your initial estimate to actual. If you are consistently off in the same direction (too low or too high) by the same factor, apply that factor as a correction next time. This is calibration, and it is the single fastest way to become a developer whose estimates are trusted.

Mental model

Mental model

Continue exploring

Tags