Skip to content

The Preamble tells you how to think together. Design rules tell you what to build. They're eighteen principles, none of them new, most of them decades old, organized into four clusters: Clarity, Simplicity, Resilience, and Extensibility.

They're not laws. They conflict with each other sometimes, and when they do, the conflict itself is useful. Simplicity says keep it minimal. Extensibility says design for the future. The tension between those two is where good architecture decisions happen. The point isn't to follow every rule mechanically. It's to have shared language for the trade-offs.

When a code review says "this violates Clarity" or "we're trading Simplicity for Extensibility here," everyone knows what that means. The rules give disagreements a technical vocabulary instead of devolving into taste arguments.

What follows is the essential idea behind each rule. Not the full reference (that's in the Command Reference), but enough to internalize and carry with you.

Clarity

Design for understandability

1. Clarity over cleverness

When you have a choice between a clever solution and a clear one, choose clear. Clever solutions impress the author. Clear solutions serve everyone who reads the code afterward, which is a much larger group over a much longer time.

Code is read far more often than it's written. Explicit variable names beat cryptic abbreviations. Simple control flow beats nested ternaries. The math works out: one author, ten readers over three years. Clarity pays compound interest.

2. Least surprise

In interface design, always choose the behavior users would expect. Surprise is context-switching. When an API behaves unexpectedly, developers stop working and start debugging. Expected behavior is automatic. Unexpected behavior is cognitive load.

Don't write a map() function that deletes elements. Don't return 200 with an error body. Use conventions. Do what's expected.

3. Silence

When there's nothing to say, say nothing. Programs should be quiet unless they have something important to communicate. A deployment that succeeds produces zero output. A deployment that fails produces a clear error. Not the reverse.

When everything outputs constantly, important signals disappear. Real problems get drowned in chatter.

4. Representation

Fold knowledge into data, not code. Make the data structure so clear that the logic becomes obvious. Don't represent user roles as strings you check with if role == "admin". Represent them as an enum. Now the compiler catches your mistakes instead of production.

Logic is hard to reason about. Data structures are easy to reason about. When you push knowledge into data, the program becomes obviously correct instead of mysteriously working.

Simplicity

Design for discipline

5. Simplicity

Design for simplicity. Add complexity only where you must. Every line of code is a liability: someone has to read it, debug it, test it, maintain it. A simple system with a known limitation is more reliable than a complex system that tries to handle everything.

This requires discipline. "Let me add support for X even though we don't need it yet" is the most expensive sentence in software engineering. That feature might never be needed. The maintenance cost is paid every day it exists.

6. Parsimony

Write big programs only when clearly nothing else will do. Before building a monolith, prove that three focused small programs won't work instead. Before writing a framework, prove a library won't do. The burden of proof is on complexity, not simplicity.

7. Separation

Separate policy from mechanism. Separate interfaces from engines. Keep "what should happen" apart from "how it happens." When you mix abstraction levels, changes ripple everywhere. When you expose implementation details, clients depend on them. You lose the ability to change anything without breaking everything.

8. Composition

Design programs to be connected to other programs. Build components, not monoliths. Make your output useful as someone else's input. A linting tool that writes JSON can be used with any downstream tool. A tool that writes HTML can't be piped to anything.

The Unix philosophy in four words: do one thing well. Design for composition and you get reusability, modularity, and flexibility without trying.

Resilience

Design for reliability and evolution

9. Robustness

Robustness is the child of transparency and simplicity. You don't build robust systems by adding error handling everywhere. You build them by making systems so transparent and simple that errors are obvious and handling is straightforward.

The paradox: systems that fail loud and fast feel fragile. Systems that hide errors feel stable. Until they corrupt your data.

10. Repair

When you must fail, fail noisily and as soon as possible. Don't silently return null. Throw an exception. The exception tells you where the real problem is. Null hides the problem until it causes cascading failures three layers down.

Silent failures compound. By the time you discover the problem, you've processed corrupted data for hours. Loud failures let you fix things at the source, while the blast radius is still small.

Error messages should tell the consumer what to do next, not just what went wrong. "Element not found" leaves you stuck. "Element not found. Available elements: [list]. Run snapshot to refresh." That tells you the next step.

11. Diversity

Distrust all claims for "one true way." Microservices aren't always better than monoliths. Sometimes a monolith is the right choice. Any claim that there's one best way to do something is probably wrong. Most meaningful choices have trade-offs. Understand them instead of following dogma.

12. Optimization

Prototype before polishing. Get it working before you optimize it. Most programs spend 80% of time in 20% of the code. Optimizing randomly costs you everywhere and helps nowhere. Measure first. Profile to find the real bottleneck. Optimize only the bottleneck. Document why.

Extensibility

Design for long-term growth

13. Modularity

Write simple parts connected by clean interfaces. A payment module doesn't know about logging. Logging doesn't know about payments. They communicate through agreed-on interfaces. Modular systems are easier to understand, test, change, and reuse. One module at a time, one concern at a time.

14. Economy

Programmer time is expensive. Conserve it over machine time. A slow program that you can understand and modify is more valuable than a fast program that's impossible to understand. Use high-level languages. Use libraries. Let the computer do grunt work.

15. Generation

Avoid hand-hacking. When you're doing the same thing repeatedly, write a program to do it. Hand-hacked code is full of subtle variations, copy-paste mistakes, inconsistencies. Generated code is consistent. One mistake in the generator is one fix. One mistake in hand-written code is one mistake per instance.

16. Extensibility

Design for the future, because it will be here sooner than you think. Systems outlive your assumptions about them. Clean interfaces enable new uses. Modular design enables new components. Documentation of assumptions enables future understanding.

17. Transparency

Design for visibility to make inspection and debugging easier. You can't fix what you can't see. Systems should be observable without special tools, without a debugger, without asking the person who wrote it. Logs, metrics, traces. Make the state obvious.

The Eighteenth Rule

18. Attention is a finite resource

This rule came later than the others, but it ties them together. Every interface, every notification, every configuration option consumes someone's attention. Design systems that respect this. Silence over noise. Defaults over choices. Calm over urgency.

Rules 3 (Silence), 1 (Clarity), and 5 (Simplicity) all serve attention respect. Rule 18 makes the connection explicit: human attention is the scarcest resource in any system.

A system that requires its operator to watch a dashboard constantly is a system that disrespects attention. A CI pipeline that sends forty notifications per deploy is a system that has given up on signal. Build things that stay quiet when everything is fine and speak up clearly when something isn't.

When Rules Conflict

They will. Simplicity says keep it minimal. Extensibility says plan for growth. Clarity says write more code if it's easier to read. Economy says use a library even if it's less clear. These tensions aren't bugs in the framework. They're the framework working.

When you hit a conflict, name it. "We're trading Simplicity for Extensibility here because we know this component will need to support three more data sources by Q3." That sentence is more valuable than any design document. It captures the decision, the trade-off, and the reasoning in one breath.

The Preamble from Chapter 1 makes this possible. Peer thinking lets you surface the conflict without it becoming personal. The design rules give you concrete language for the trade-off.

Philosophy and principles are only as useful as the daily practices that carry them. The next chapter covers what that looks like: a three-step ritual that puts these rules into motion every time you write code.