A catalog of idioms, phrases, principles, mentalities, and philosophies to adopt, share, and discuss. Universal first, language-specific examples second. Every entry is one line of definition, one line of so what, and a pointer if you want the deep dive.
This is a living document. If an entry doesn't earn its place β if you can't explain it to a junior in one breath and defend it in a design review β cut it. The list is opinionated on purpose: there is no neutral "best practice," only tradeoffs you've chosen to name.
It is organized by lifecycle stage, because advice without a stage is advice you'll never remember at the moment you need it. The map below shows what's covered where β including the stages most such lists neglect, like requirements, release, and retirement.
- The Software Lifecycle Map
- Part I β Foundational Axioms
- Part II β Requirements & Scope
- Part III β Engineering Practices
- Part IV β Architecture & Design
- Part V β Language & Runtime Idioms
- Part VI β Release, Deployment & Rollout
- Part VII β Operations & Observability
- Part VIII β Performance & Scale
- Part IX β Security Mindset
- Part X β Team & Process Mentalities
- Part XI β Cross-Cutting Concerns
- Part XII β Retirement & Decommission
- Part XIII β Software as a Craft
- Part XIV β The Canon: Primary Sources
- Verifying Quotes and Claims
- Contributing
Three ways in, depending on why you're here:
- Onboarding β read Part I, Part XIII (Software as a Craft), and the Lifecycle Map above. That is the load-bearing 10%.
- Design review β jump to the section for the decision in front of you. Name the tradeoff out loud before you pick a side.
- Team ritual β pick one entry per week. Read it, argue about it in standup, decide whether your codebase already obeys or violates it, and write down the verdict in an ADR. That last step is the one that makes it stick.
A note on false balance. Some entries below are genuinely contested β "microservices," "event sourcing," "TDD." Others are close to settled β "don't commit secrets," "measure before optimizing." The markers below tell you which is which:
| Marker | Meaning |
|---|---|
| βοΈ | Contested. Reasonable people disagree. Argue it out. |
| β | Broadly settled. Deviating requires a stated reason. |
| π | Tool/technique, not a principle. Useful, contextual, eras. |
| π¬ | A phrase worth saying out loud in a design review. |
This catalog is organized to match how software actually gets built, so that every stage has a home and the gaps are visible rather than assumed. The βοΈ/β markers still apply per entry.
| Stage | Part | What's there |
|---|---|---|
| Ideate & scope | II | Specs as hypotheses, acceptance criteria, vertical slices, non-goals, ADRs |
| Design & architect | I, IV | Complexity, coupling, boundaries, SOLID, patterns, Unix/functional/distributed philosophy |
| Build | III, V | Testing, VCS, refactoring, review, plus per-language idioms |
| Release & deploy | VI | Expand/migrate/contract, feature flags, progressive delivery, rollback, SemVer |
| Run & operate | VII | Observability pillars, SLOs, backups, RTO/RPO, runbooks, load-shedding |
| Optimize | VIII | Measure-first, Amdahl, locality, batching, tail latency |
| Secure | IX | Threat modeling, authN/authZ, secrets, supply chain, secure-by-default |
| Sustain & grow | X | Agile, technical debt, estimation, product empathy, team ownership |
| Cross-cutting | XI | i18n, accessibility, configuration, dependency management |
| Retire | XII | Sunsets, deprecation policy, Strangler Fig, data retention, deletion |
| Mindset & canon | I, XIII, XIV | Axioms, proverbs, anti-wisdom, ethics, primary sources |
The two parts that most lists under-serve are here because they're where the expensive mistakes live: II (Requirements) and XII (Retirement). Nearly every part of a system is built long before anyone discovers what it should have been, and every codebase eventually becomes an archaeological site. Planning for both is cheaper than archaeology.
- β Occam's Razor / Minimum Solution β Solve the problem that exists, in the fewest concepts that fully solve it. So what: every abstraction is a permanent maintenance liability you must pay rent on.
- β KISS β Keep It Simple, Stupid β Simplicity is a feature, not a compromise. So what: "it's more general" is not an argument for shipping something complex today.
- βοΈ YAGNI β You Aren't Gonna Need It β Don't build for imagined futures. Counter-argument: in some domains you are going to need it, and the cost of retrofitting is catastrophic (protocols, schemas, public APIs). The real lesson: YAGNI is about capability, not about flexibility.
- βοΈ Principle of Least Astonishment β The system should behave the way a competent practitioner of its language expects. Surprise is a bug, even when it's "clever." So what: read the ecosystem's conventions before you invent your own.
- β
The Pit of Success β Design so the easy path is also the right path. If the correct usage is the laborious one, the design has failed. (Object-C's
inithierarchy, Rust's ownership-by-default, Go's zero-value usefulness.) - π¬ "It's not complicated, it's just not written down yet." β Often true. A wall of tangled code is usually a missing model, not a hard problem.
- π Flyweight β Store shared immutable state, pass around references. Cheap objects over duplicated state.
- π YAGNI-adjacent: Rule of Three β Abstract after the third real instance. One is a coincidence, two is a pattern, three is a design.
- β
The Law of Demeter / Principle of Least Knowledge β A method may only touch its own object's data and the data of objects handed directly to it. No
a.getB().getC().setD(). So what: changeCinternals without hunting for every three-hop chain. - β Principle of Least Privilege β Every component, key, and token gets the minimum access it needs, for the minimum time. So what: the blast radius of any single compromise shrinks to something survivable.
- β The Principle of Locality / Information Hiding β Hide the decisions most likely to change behind stable interfaces, and keep them together. (Parnas, 1972 β "On the Criteria To Be Used in Decomposing Systems into Modules." This is arguably the most important idea in the list.)
- βοΈ DRY β Don't Repeat Yourself β Every piece of knowledge has one authoritative expression. Nuance: DRY is about knowledge, not text. Two coincidentally-identical functions encoding different reasons are fine; one regex encoding six different rules is not.
- β Rule of Three (again, worth twice) β Wait for the third repetition before you abstract.
- π The Rule of Three, Empirical Form β Glass's Fact #18 gives the reuse version of this, and it's the best available evidence for the heuristic: there are two rules of three in reuse β (a) it is three times as difficult to build a reusable component as a single-use one, and (b) a component should be tried in three different applications before it's general enough to belong in a reuse library. (Robert Glass, Facts and Fallacies of Software Engineering, 2002, Fact #18.) Note that (b) means the classic "third repetition" rule is a lower bound for a production abstraction, not a green light.
- π Composition Over Inheritance β Prefer composing behavior to subclassing behavior. So what: avoids fragile base-class taxonomies where a subclass changes you didn't predict. (Effective Java, Item 18.)
- π Liskov Substitution Principle β If
Dogis substitutable forAnimal, it must truly be one. Violations (notably mutable collections asList<T>) are where type systems stop helping. - π¬ "Make illegal states unrepresentable." β The best type system is the one that deletes a class of bugs rather than documenting it. (The phrasing is usually credited to Erik Meijer / the Haskell and ML community, and popularized through Yulii's talk; treat the attribution as loose. The idea is not: model a domain as a sum type and half the bug reports stop existing.)
- β High Cohesion, Low Coupling β Modules that do one job; modules that need to know little about each other.
- β Design for Replacement β Assume every dependency will be swapped, forked, or replaced within 18 months. It usually is. This is why dependency injection and thin interfaces exist.
- βοΈ Distributed Monolith / Microservices β Splitting a codebase across the network is a real tradeoff, not a free win. You trade in-process calls for network failure, distributed transactions, deploy coupling, and observability debt. Ask: would a module boundary in one process with a hard interface check satisfy the actual reason for splitting? (Team autonomy, independent deploys, scaling profile, compliance boundary β pick one reason.)
- β Parse, don't validate β Data at the boundary becomes a rich typed structure, not a bag of strings you re-check everywhere. So what: you can then make invalid states unrepresentable, and you validate once, at the edge.
- π Anti-Corruption Layer β Wrap a foreign/legacy model so its weirdness stops leaking into your domain.
- β
Names Should Say What, Not How β
getActiveCustomersByRegionSincebeatsqueryData(). Rule of thumb: if you need a comment to explain a name, the name is wrong. - β
Avoid Synonyms and Homonyms β If you have
Manager,Handler,Controller, andService, you've created four meaningless words. Pick one and use it everywhere. So what: a reader who knows one knows all four. - β Say the Thing β A name that says what the thing is rather than what it does ages better. (Kent's "The Name of the Thing.")
- β
Don't Over-Word β
UserbeatsUserObjectandIUser. Prefixes that describe a category (I,Impl,Base,Abstract) usually mean the naming is still a category the reader must learn. - π¬ "If the name is wrong, you are not allowed to write the class." (Sandi Metz, paraphrasing.)
- π Boolean names should read as predicates β
isActive,hasAccess,canRetryβ soif (x)sites read as English sentences.
- βοΈ Make Illegal States Unrepresentable β Design types so that
Email | UnverifiedEmailisn't a thing you can accidentally construct. (Algebraic Data Types, type-state patterns.) - β Immutability by Default β Objects that don't change can't be corrupted, can't be observed mid-update, and are free to share. (Persistent data structures in Haskell, Kotlin, Clojure, Elixir; Rust's ownership is immutability-by-scope.)
- β The Pit of Mutable Shared State β The #1 source of heisenbugs, race conditions, and "works on my machine." Pass data; transform it; return new data.
- β Single Source of Truth (SSOT) β One authoritative home for each fact. Derived data is derived, on demand, never stored twice. So what: eliminates a whole class of sync bugs.
- β Idempotency β Operations designed to be safely retried. Because they will be retried: timeouts happen and you can't distinguish "never ran" from "ran but the response was lost." Practical consequence: idempotency keys, dedup on delivery, UPSERTs.
- β Time is the Hardest Part β Time zones, DST, leap seconds, clock skew, and monotonic-vs-wall-clock are not edge cases. Store instants in UTC, store durations as numbers, and never do date math on strings. (Joel Spolsky, "The Death of Date".)
- β Ordering and Delivery Semantics Are Assumptions β "Exactly once" doesn't exist end-to-end. It becomes at-least-once + idempotency, or at-most-once + explicit gaps. Say which one your system is, out loud, on the architecture diagram.
- π Copy-on-Write / Persistent Data Structures β Structural sharing makes "new version" cheap.
- π Seam for Time β Inject a clock. Don't call
now()deep inside logic you want to test.
- β Fail Fast β Detect and report errors at the point they occur, not three layers later with corrupted context. This is the core argument for validation at construction/parse time.
- βοΈ Exceptions vs. Result/Either Types β Exceptions are invisible in the signature and convenient; Result/Either are explicit and composable. Both are right β use exceptions at boundaries, typed results in domain logic. (Rust's
Result; Scala'sEither.) - β
Don't Swallow Errors β An empty
catch {}converts a crash into silent corruption. If you can't handle it, propagate it with context. - β Error Messages Are for the Operator β Include what failed, the identifier involved, and the next action. The user sees the operation; the on-call engineer sees the message.
- β
Context-Rich Propagation β Add information as an error travels up:
fmt.Errorf("load order %s: %w", id, err). So what: the difference between a 5-minute and a 5-hour incident is usually one good error message. - βοΈ Exceptions as Control Flow (Scala, Clojure, Elixir) β Powerful when genuinely exceptional, misused when it becomes your primary branching mechanism. The line: is this expected?
- β Retry with Backoff and Jitter β Never retry immediately in a tight loop; you turn a blip into an outage. (Google SRE Workbook ch. 22.)
- π Circuit Breaker β Stop calling something that's down so you don't die with it.
- β The Twelve-Factor App β Twelve criteria for building portable, scalable, deployable services. The parts that repay reading: config is in the environment (never in code), backing services are attachable resources (the database is a resource you swap, not a hard dependency), disposability (processes must be crashable and cheap to start), and dev/prod parity. (Adams, 2011, 12factor.net.) Useful as a checklist and excellent as a vocabulary β "that's not a backing service, that's a hard dependency" is a real design review line.
- π Failover vs. Fallback β Know which one you built, and whether the fallback is genuinely correct or just a different wrong answer.
- β Program to Interfaces, Not Implementations β Depend on the contract, not the machinery behind it. (Go proverb: "Accept interfaces, return structs.")
- β
Principle of Explicit Contracts β A function's contract β preconditions, postconditions, side effects β is part of its name, type signature, and documentation. If it has a side effect, say so:
save,write,flush,invalidate,close. - β Tell, Don't Ask β Ask an object to do something rather than interrogating its data and reassembling its behavior elsewhere. So what: behavior stays with the data that has it.
- β
Null is a Smell; use absence deliberately β Null,
None,nil,undefined, and empty collection mean four different things. Be explicit about which. - βοΈ Parsing vs. Consuming β Consume streams incrementally and as you go; parse into a structure to analyze repeatedly. So what: decides whether a 2 GB file is a memory leak or a 30-second job.
- β The Boundary Is Where You Validate β Everything crossing a process/network/user boundary is untrusted. Parse it into a trusted type immediately, and let the rest of the system assume well-formedness.
The stage most lists skip, and where the most expensive mistakes are made. Everything here is downstream of a decision you already made.
- β The Spec Is a Hypothesis, Not a Contract β You are writing down a belief about what's valuable, to be tested against reality. Writing it in confident declarative prose is how it stops looking like a guess. So what: if the doc can't be wrong, nobody will check it.
- β Definition of Ready β Before work starts, agree what makes it startable: the problem is understood, the acceptance criteria are written, the unknowns are named. So what: without it, "ready" means whatever the person who said it meant, and mid-sprint discovery is the most expensive kind.
- β Acceptance Criteria Are the Real Spec β Prose descriptions are aspirations; acceptance criteria are checkable. Write them as the tests you'd write, because that's exactly what they should become. (Given-When-Then is popular, but the requirement is testability, not the syntax.)
- β User Stories Are Unit of Negotiation β "As aβ¦, I wantβ¦, so thatβ¦" is a reminder of a conversation, not a spec. Its value is that it names the user and the goal. Its danger is that a well-formed story still contains no information. (Jeff Patton, User Story Mapping.)
- β Vertical Slices, Not Layers β Deliver one end-to-end path that works, not the entire data layer followed by the entire UI. A slice touches every layer, so it proves the architecture. (This is the "walking skeleton" / "tracer bullet" idea β Kent Beck's TDD by Example popularized the tracer bullet: aim a test that fails, then build the minimum to pass it.)
- β Explicit Non-Goals β Write down what you are not doing and why. This is the highest-leverage line in most design docs, because the unstated thing is the thing someone will assume is merely forgotten. So what: it converts a future argument into a future question.
- β Ask About the Past, Not the Future β People are terrible at predicting their own future behavior and excellent at recalling the last time. Every requirements interview should be about a real, recent, specific instance. (Rob Fitzpatrick, The Mom Test β a book about how to interview without leading, which is really a book about requirements elicitation.)
- β Users Are Experts in the Problem, Not the Solution β "Add a button" is a request, not a requirement. The requirement is the job behind it. So what: building the requested solution confidently is the most common way to build the wrong thing perfectly.
- β Reduce the Cost of Being Wrong, Don't Eliminate It β Uncertainty is irreducible; the engineering question is only how expensive a wrong guess is. Spikes, prototypes, MVPs, feature flags, A/B tests β these are all the same move: buy information cheaply before committing. (Product safety, Sterrett; Ries, Lean Startup.)
- β Unhappy Paths Are Where the Requirements Live β The happy path is usually obvious once specified. The requirements work is: what happens on partial failure, double submission, cancellation after fulfillment, concurrent edit, expired credential, clock skew, partial refund? So what: the happy path gets built in a day; the unhappy paths are the actual product, and they're where the incidents come from.
- β Requirements Rot Like Everything Else β A spec that isn't attached to code and tests goes stale, and worse, it stays believed. Keep it in version control next to the code, and treat the executable tests as the authoritative version.
- βοΈ Write It Down vs. Just Build It β Writing specs is expensive and specs invite becoming attached to a plan that reality invalidates. Building without any spec is also expensive, and produces code nobody can navigate. Synthesis that actually works: a one-page design doc for anything that crosses a module boundary, and no doc for anything smaller. See ADR practice below.
- π The ADR β Write Down the Decision, Not the Plan β Context, options considered, decision, consequences, date. Fifteen minutes. The "options considered" section is the one that pays off two years later, when someone proposes the thing you rejected and asks why not. (Michael Nygard's ADR format; popularized by ThoughtWorks.)
- π Pre-Mortem β Before starting, ask: "It's six months later and this failed. Why?" Imagined failure produces more and better risk discussion than asking for risks, because failures are easier to discuss than probabilities. (Gary Klein.)
- π Traceability β Link requirement β design β test β commit. It's bureaucracy at small scale and genuinely lifesaving at large scale (regulated, safety-critical, or multi-team).
- π¬ "The requirements will change." β Not a defeatism; a planning assumption. If you believe it, you build for change (loose coupling, versioning, backward compatibility). If you don't believe it, you're not allowed to be surprised later.
- π¬ "Say no early, kindly, with a reason." β Scope creep is a negotiation failure, not a requirements failure. The cheapest time to cut scope is before anyone has built anything.
- β Test Behavior, Not Implementation β Tests coupled to internals break on every refactor and resist change. Test what the thing does, from the caller's perspective. (Kent Beck, "Implementation Driven Development is Dead"; Michael Feathers, "Working Effectively with Legacy Code".)
- β Test Pyramid β Many fast unit tests, fewer integration tests, a few end-to-end tests. So what: the E2E suite is your most valuable and most expensive safety net; spend it on the top few journeys only.
- β TDD: Red, Green, Refactor β Watch the test fail for the right reason, make it pass, then clean up with the test as your safety net. The hidden benefit isn't coverage β it's that the test is written before the design ossifies.
- β Arrange, Act, Assert β Three distinct phases. When they blur, tests become unreadable and can't be trusted.
- β One Reason to Fail β A test asserting ten things tells you nothing when it goes red. Assert one behavior per test; the arrange can be rich.
- β Test the Contract, Not the Current Behavior β Especially for the behavior you consider ugly but must preserve: those are your refactoring safety net. (Characterization tests, Michael Feathers.)
- β The Test Pyramid Inverts for Legacy Code β In a legacy codebase, build a safety net first before touching anything. That's the whole game. Cover at the seams (Michael Feathers' seams technique).
- β Make the Hard to Test Impossible β If a thing is hard to test, that's usually a design smell: hidden state, hidden time, hidden network, hidden globals. The test difficulty is the signal.
- β Deterministic Tests β No reliance on wall-clock time, real network, execution order, or test pollution. If it flakes, it's not a test, it's a coin flip.
- β Test Data Should Be Obvious β Use builders/factories for the boring parts, name the one interesting value. Reviewers should see what matters.
- β Coverage Is a Means, Not a Goal β 100% line coverage can coexist with zero tested behavior. Cover what could hurt, especially money, auth, and data loss.
- βοΈ Mock at the Edges, Not the Internals β Mocking your own collaborators is a design smell; mocking the network/clock/filesystem is a testing tool. Rule: if you can't test without mocking, ask what got coupled to what.
- π Golden/Snapshot Testing β Great for stable, large structures. Useless if you regenerate snapshots without reviewing diffs.
- π Property-Based Testing β Hypothesis, fast-check, proptest. Find edge cases you didn't think of by stating invariants instead of examples.
- π Metamorphic Testing β Test relations between inputs (shuffle-invariant, add-one-invariant). Works where examples are hard to write.
- π Mutation Testing β Verify your tests actually catch bugs by deliberately breaking the code. The highest-signal use of coverage data.
- π¬ "Test doubles are a design smell, except at the edges." (Sandi Metz.)
- π¬ "The best time to write a test was before; the second best time is now, to enable the refactor you're about to do."
- β
Small, Focused Commits β One commit, one logical change, one message explaining why. So what:
git bisectandrevertwork; reviews are reviewable; the history is documentation. - β Write Commit Messages for the Future You β The future you is a stranger with no context and full write access. Explain the motivation, not the diff (the diff is already there).
- β Don't Commit Generated/Vendor/Build Output β Except when the output is the deliverable (checked-in lockfiles are a deliberate exception; they're a feature).
- β
Never Force-Push Shared Branches β
git push --force-with-leaseon your own topic branch is fine; onmainit is an act of war. - β Rebase Your Own Work, Merge Shared Work β Keeps shared history honest and linear.
- β Branches Are for Coordinating Humans β Branch-per-feature is a communication mechanism, not a sacred rite. Trunk-based with small batches works too.
- β The Golden Rule of Version Control β Don't rewrite other people's history. Ever. Even if you think you're fixing a mess.
- β
Keep the History Clean but Not Holy β
git filter-branchand friends are for history rewrites with a clear, rare justification β removing a leaked secret, not tidying. - βοΈ Feature Branches vs. Trunk-Based β Trunk-based (short-lived branches, frequent integration, strong CI) generally wins on integration cost; feature branches win on review depth and incomplete work. Pair it with test what you claim to test in CI.
- π
git bisect,git blame,git log -Sβ Your three archaeology tools.git log -S(pickaxe) is the one people miss: find when a string appeared or disappeared. - π Bisect Faster β Automated
git bisect runwith a script, even a crude one, beats manual bisection. For performance regressions, bisect a benchmark. - π¬ "A clean history is a sign of a clean mind." (Attributed, variously, to a lot of people.)
- β Refactoring Changes Structure, Not Behavior β Same tests pass before and after. If behavior changes, it's a feature or a bugfix, not a refactor.
- β Two Hats β Martin Fowler: wear the refactoring hat (no behavior change) or the functional hat (behavior change, no cleanup), never both at once. So what: the tangled commit that does both is unreviewable and unrevertable.
- β Refactor in Small Steps, Commit Often β Each step is green, reviewable, and revertable. The final state may be months away; the intermediate states must be safe.
- β Boy Scout Rule β Leave every module cleaner than you found it. Not a grand refactor β one thing, on every visit, forever. This is the single most effective maintenance habit.
- β The Compiler Is Your Refactoring Assistant β Rename-anything, extract-function, inline, and safe type changes are the easy refactorings. Do them constantly; they cost nothing and keep the code close to its original intent.
- βοΈ Technical Debt Is a Loan, Not a Sin β Fowler: debt is deliberate taking a shortcut for a reason, usually to hit a date. Unprincipled messiness is not debt, it's just mess. Different problems, different responses.
- β YAGNI vs. Debt β Deleting unused code is not debt repayment; it is the repayment. Dead code has ongoing interest and zero principal.
- π¬ "Delete is the most underused refactoring." β If a flag, class, or config option has only one value in practice, it's not a feature, it's a branch you have to test forever.
- π¬ "Don't refactor code you're about to delete."
- β Review the Change, Ask the Question β "Why this approach?" is worth more than "nit: rename this." Style is a linter's job, not a reviewer's.
- β Review the Diff in Context β Read the file, not just the hunk. The bugs are almost never inside the changed lines; they're in the assumptions the changed lines make.
- β Review for Correctness, Then Design, Then Style β In that order. Approving the right thing in the wrong shape is a wasted conversation; approving the wrong thing politely is worse.
- β Review Latency is a Feature β Respond within a day. A review queue is a deployment blocker. (See "Stop the Line.")
- β Praise in Public, Criticize in Private β And prefer critique of the code to critique of the person, always.
- β Automate the Mechanical β Formatting, import order, lint rules. Every review comment a bot could have made is a review comment a human didn't have to spend attention.
- β Review Is Not Approval Of the Author β You're signing off on the change, not on your colleague's judgment. "LGTM" is a technical statement, not a relationship.
- β Rubber-Stamping Is Worse Than No Review β A review that hasn't read the diff actively destroys the signal everyone relies on. If you can't review it, say so and don't approve.
- π¬ "Review the code, not the person. The code didn't choose to be written."
- β The Best Documentation Is the Code β Clear names and types answer most questions. Reach for a comment only to explain why, or to note a constraint the reader cannot see.
- β Explain the Why, Not the What β The what is in the diff. The why evaporates from the codebase the moment the PR merges, unless you wrote it down.
- β Comments Rot; ADRs Persist β Architecture Decision Records β the decision, the options considered, the reason, the date β are the highest-value documentation a team can produce, and they take 15 minutes.
- β
Document the Invariant, Not the Implementation β "This must stay sorted by
effective_date" survives refactors. "We use a bubble sort here" does not. - βοΈ README as Entry Point vs. Living Docs β A README that tries to be everything becomes nothing. Keep it a map; put depth in linked documents that are allowed to go stale gracefully.
- β Document the Errors, Not Just the Successes β The most valuable doc section is "here's what this does when things go wrong."
- π¬ "Only comment the parts you can't make obvious." (Rob Pike; also Jeff Erlanger's "Code should read like prose.")
Robert C. Martin's five principles. Widely cited, frequently misapplied. Honest take: SRP and ISP are the high-value ones in practice; OCP is the most often-mangled (it gets cargo-culted into a "no if statements" rule that produces worse code).
- β S β Single Responsibility Principle β A module has one reason to change. So what: the reason is a human concern, not "does one thing." Ask "which single stakeholder's requirement does this serve?" (Robert Martin.)
- β O β Open/Closed Principle β Open for extension, closed for modification. Practical reading: add behavior by adding code, not by editing and retesting existing code.
- βοΈ L β Liskov Substitution Principle β Subtypes must be usable anywhere the base type is, without the caller knowing. The famous violation: allowing a
Stack'spushto break the LIFO order inherited fromList. - β I β Interface Segregation Principle β Prefer small, specific interfaces over one fat one. So what: clients stop depending on methods they don't use, so a fat interface can never evolve freely.
- β D β Dependency Inversion Principle β Depend on abstractions, and the detail should depend on the abstraction, not the other way around. This is the principle that makes testing, pluggability, and framework independence possible.
- β Principle of Least Knowledge / Law of Demeter β See Part I. One dot per method. (Karl Hoehn, The apgraphics article, 1989.)
- βοΈ The Principle of Command Query Separation β Commands change state and return nothing meaningful; queries return data and change nothing. (Bertrand Meyer.) Following it makes a shocking number of bugs impossible; ignoring it is a real source of "phantom update" bugs.
- β Principle of Expressive Code β The code should express the intent directly, in one obvious way. (Jacques Carel.) If you need a comment, the code failed. (The book of that name has practical recipes; e.g. "assert instead of if-then-throw".)
- β The Principle of Proximity β Related things should live together, and the closer the more related. (The Art of Readable Code.)
- β Reuse at the Right Altitude β Reuse a concept when it's stable and shared; write it out when it varies on purpose. (Sandi Metz.) Three copies of similar-but-deliberately-divergent code is often better than one over-parameterized abstraction.
- β
Principle of Least Knowledge Applies to APIs Too β Don't leak the internal representation. Return
List<LogEntry>, not a mutable internal list; return a value object, not raw fields. - π Acyclic Dependencies Principle β Your package/module graph must be a DAG. This is the practical, checkable form of a lot of "good architecture."
- π Stable Dependencies Principle β Depend on things that change less often than you do. (Robert C. Martin.) A quick way to spot bad coupling.
- β Deep Modules β A module with a simple interface hiding substantial functionality. The cost of a module is its interface; its benefit is its functionality. Shallow modules (thin wrappers, pass-through layers, "manager" classes that only forward calls) add interface cost with no payoff. This reframes SRP usefully: "do one thing" should mean "do one thing fully," not "be small." (John Ousterhout, A Philosophy of Software Design, 2018.)
- βοΈ Strategic vs. Tactical Programming β Tactical: get something working as fast as possible. Strategic: think about the design, then build. Ousterhout's warning is that tactical programming is a one-way door β it's very hard to switch to strategic later, because the mess is already load-bearing. His sharpest phrase for the person who embodies the failure mode: the "tactical tornado" β fast, effective in the moment, and leaves a wake of destruction for everyone else.
- β Design It Twice β Your first design is rarely your best. Generate two or three genuinely different approaches, compare them, then pick. Ousterhout's own Tk toolkit API was a direct application of this. Cheap relative to a rewrite, and the single highest-leverage habit in his book.
- β
Define Errors Out of Existence β The best way to handle an error is to design so it cannot occur. Prefer a total function over a partial one; prefer an impossible state over a handled one. (Ousterhout, APOSD ch. 8.)
β οΈ Caveat he himself flags: this is not a license to delete necessary error checks β deleting a check without removing the possibility is just hiding the bug. - β Different Layer, Different Abstraction β A file parser and a UI should not share an abstraction just because they both touch files. Forcing shared abstractions across layers ("pass-through" methods to make things look uniform) is a common source of shallow-module proliferation.
- β Pull Complexity Downward β Push details into lower-level modules so higher layers read as clean intent. Also: general-purpose modules are usually deeper than special-purpose ones, because they amortize design effort across more uses. (Ousterhout, ch. 6 β notably expanded in the 2nd edition, 2021.)
- βοΈ Method Length: Clean Code vs. Ousterhout β Robert Martin: "functions should do one thing," short methods, extract aggressively. Ousterhout: this is taken too far and produces fragmented code that's hard to follow; a long, clearly-written method that does one coherent job is fine. Both are right about the goal (clarity) and disagree about the lever. Practical resolution: judge methods by whether reading them top-to-bottom tells a coherent story, not by line count. (Ousterhout addresses this disagreement head-on in the 2nd edition.)
- β Passphrase: "The greatest limitation in writing software is our ability to understand the system." β Ousterhout, APOSD ch. 1. Why it matters: it reframes complexity management as a human-cognitive problem, which is why a change that is "obviously simple to the author" can be incomprehensible to everyone else.
Patterns are solutions with names, so you can say "this is a Strategy" instead of re-explaining it. They're a shared vocabulary, not a checklist. Rule of thumb from Fowler: patterns are for people who have already solved the problem and want to discuss it β if you're reaching for a pattern before you understand the problem, you probably need a simpler answer.
- π Strategy β Swap interchangeable behavior behind a common interface.
(collection.sort(|a,b| ...)),formatting options. - π Adapter β Make an incompatible interface fit yours.
(YourAppCode β LegacyPayoutSystem). - π Facade β One simple entry point over a complicated subsystem. (Generally good. Also the tool of choice for hiding a vendor SDK.)
- π Decorator / Wrapper β Add behavior by composition, at runtime, in a stack. (The single most useful pattern in logging, I/O, and retry.)
- π Factory / Abstract Factory β Centralize "how do I get one of these," hide the construction details. (Go proverb: "A little copying is better than a little dependency.")
- π Observer / Publisher-Subscriber β Notify many dependents of a change, without coupling them to the notifier.
β οΈ Beware: the async version silently turns your system into a distributed system with abusin the middle. - π Singleton β One instance, globally accessible.
β οΈ Almost always a worse idea than dependency injection. Theglobalkeyword in Go exists for a reason, and the discussion about it is instructive. - π Builder β Assemble a complex thing readably. (Go proverb: "If you have a type with a
+method, use+." β Go's functional-option pattern.) - π Repository β A collection-like interface over persistence.
β οΈ Often over-applied: see below. - π Iterator / Generator β Traverse a collection without knowing its representation. (Present in every language; often the right answer to "how do I return an unknown-size result".)
- π Template Method vs. Composition β Inheritance-based vs. composition-based. Almost always prefer composition; that's the whole point of the reframe.
- π Proxy / Decorator for Remote β Local stand-in for a remote thing. (gRPC stubs,
HttpClient.)
- βοΈ Repository Pattern β A generic CRUD interface over a database is often a worse anti-pattern: it hides the database, hiding its strengths, and produces a second query language that's easier to get wrong. Prefer: query directly for reads; use repositories when the domain genuinely needs to abstract storage (multi-backend, complex aggregates, testing).
- βοΈ Event Sourcing β Append-only event log as the source of truth. Powerful for audit/debug/analytics; enormous complexity in invariants, schema evolution, and projections. Use when auditability and temporal queries are the product, not because it's fashionable.
- βοΈ CQRS β Separate command and query models. Great when read and write shapes genuinely diverge. Usually a premature doubling of code.
- βοΈ Microservices β See above. The distributed monolith is real and common; if you end up with synchronous call chains across five services, you built a distributed monolith.
- βοΈ Dependency Injection Container β A DI framework is fine; a container often creates a service locator with extra steps. Rust/Go/Elixir largely abandoned them for constructor wiring, and nobody misses them.
- π Cache-Aside β Read-through / write-through / write-behind caches. All useful; all require a stated invalidation strategy. There is no free lunch, there is only a stated staleness budget.
- π Outbox Pattern β When you need a message and a DB write to be atomic. Genuinely the right answer more often than people realize. Costs a poller and a table.
- π¬ "A pattern is a name for a decision you already made. Make the decision first."
Terms that are (mostly) insults, and knowing them saves a lot of time.
- β Big Ball of Mud β No discernible structure, everything entangled. The core anti-pattern; the top-level diagnosis for most legacy systems.
- β God Object / God Class β One class that knows and does everything. Usually a missing-abstraction smell: nothing else took responsibility for its responsibilities.
- β The Swiss Cheese β Whole system, but full of holes. (The term is generally credited to Ed Yourdon, and appears in Revenge of the Servers (2002); note that the identical "holes are aligned until they line up" analogy was also used by British epidemiologist Nick Lambert in 1990 to describe the failure of multiple defensive layers in aviation accidents. Lambert's is the earlier and more rigorous version β worth reading the parallel, because the software analogy gets weaker precisely where the medical one is strong.)
- β The Pothole Effect β Bridges that are "burned over" by repeated, well-intentioned fixes. The condition, again usually attributed to Yourdon: repeated patch-after-patch repairs produce a structure that is nominally repaired and functionally worse, because nobody can safely change it anymore. The lesson: if fixes keep landing on the same spot, stop patching and change the underlying process or design. A component that breaks monthly isn't unlucky; it's telling you something structural.
- β Shotgun Surgery β One logical change requires edits in ten scattered files. Symptom of missing cohesion. The fix is rarely "be careful"; it's a change to a shared abstraction.
- β Cargo Cult Programming β Copying a pattern's form without its reasoning. (Richard Gabriel, 1994 β used to be "cargo cult programmers" in The Psychology of Computer Programming.)
- β The Law of Demeter Violation β The "Train Wreck" β One change touches everything.
- β Distributed Monolith β See above. The systems that took the worst of both worlds.
- β Premature Abstraction β Abstracting before you know the shape. Cheaper to fix than a bad abstraction, but the cost of the wrong one compounds forever.
- β Feature Creep β The scope grows during the build.
- β Technical Debt (misused) β See Fowler. Calling ordinary mess "debt" robs the word of meaning.
- β The Skeptic's Guide / "Works on My Machine" β Environment-dependent behavior. Fix with hermetic builds and reproducible tooling, not discipline.
- β Mystery Meat / Legacy Spaghetti β No names, no tests, no safe way to change anything.
- βοΈ Stochastic Testing / "It works 90% of the time" β Non-determinism in the system that is supposed to be the safety net.
- β Betamancer's "programming ain't" skepticism β Named after the Betamancer persona, a well-known voice arguing that most of the industry's confident claims about reuse, generality, and framework abstraction are unfalsifiable and mostly wrong. Useful as a stance (demand evidence for abstraction) even if you don't accept the conclusions. (Self-aware note: this entry is deliberately hedged, because the corpus of "programming ain't" writing is mostly pseudonymous blog posts with limited primary sourcing. Take the argument, not the attribution.)
- β Zawinski's Law of Leaky Abstractions β "All non-trivial abstractions leak, and all abstractions are leaky because once you're in there, you can always find something that isn't abstracted." The corollary that matters: you don't get to pick which part leaks. So don't design a system whose correctness depends on an abstraction holding. (Joel Zawinski, 2002 β a talk whose title is usually rendered "The Perils of Reuse" and whose source is notoriously hard to pin down; the idea is sound and widely cited.)
- π¬ "Not invented here" (NIH) β The most durable anti-pattern in the industry, because it hides behind "we control it" while actually costing you every fix, security patch, and platform improvement upstream gives you away. Exception, not rule: compliance, latency, or a genuinely unique domain are legitimate reasons to own it. Write the reason down.
- π¬ "Bikeshedding" β Spending disproportionate argument on a trivial, low-stakes decision while the expensive decision goes unexamined. (Origin disputed; popularized via C. Northcote Parkinson's "Why Don't We Get Jokes?" and various retellings. The metaphor: everyone has an opinion about the shed's color, silence on the foundation.) The fix is procedural: explicitly rank decisions by cost-of-being-wrong, and spend your objection budget accordingly.
- π¬ "Yak shaving" β A chain of apparently necessary tasks, each justified by the previous one, that yields nothing you can ship. The correct response is to shave the yak (build the throwaway that automates the tedium, with an explicit timebox) rather than let it consume the sprint. (Term popularized by Scott Adams, via the Stanley Dilbert comic, 1990s.)
- π¬ "The galloping goose" β see above. A reminder that whatever seems finished is usually not.
- π¬ "Cargo cult" applied to processes β Adopting Scrum's ceremonies without its purpose (inspect-and-adapt), or Agile's values without its discipline. The ceremony without the feedback loop is theatre, and theatre is worse than nothing because it consumes the time the feedback loop needed.
- π¬ "The best code is no code." β Deletion is a feature. Every dependency, config flag, and abstraction you remove is a thing you never have to upgrade, document, or debug.
- π¬ "Zero-cost abstractions" β Rust's term, and a genuinely great standard: if the abstraction doesn't cost anything at runtime, use it freely. Measure the compile-time/runtime tradeoff honestly.
From the original Bell Labs papers, notably Software Tools (Kernighan & Plauger, 1976) and The UNIX Programming Environment (Kernighan & Pike, 1984). Read these before the blog posts about them.
- β Do One Thing and Do It Well β The Unix root of all good design: narrow, composable, single-purpose tools.
- β "Expect the Unexpected" β Assume the environment, inputs, and collaborators will misbehave, and be ready. (Rob Pike.)
- β "Be Liberal in What You Accept, Be Conservative in What You Send" β (Rob Pike, on program interface design.) Accept wide variety of input, produce predictable output. Part of the philosophy of robust interfaces.
- β "Everything is a file" β Uniformity is a feature. The same interface, applied everywhere, means the same tools work everywhere. (Plan 9 extended this to "everything is a file descriptor", including network connections.)
- β Mechanism over Policy β Make the mechanism (the tool) general and the policy (the use of the tool) a thin layer on top. (Rich Hickey: the "flip side" is also a slogan β and the harder half.)
- β Composition of Small, Sharp Tools β Power from assembly, not from a single super-tool.
- β Filters β Each stage reads, transforms, writes. No shared state, no side channels, easy to test each stage, easy to reorder. (Rob Pike, "Pipes and Filters".)
- β Text as Data β Readable, greppable, diffable, scriptable, language-agnostic. CSV, JSON, s-expressions, config files, source code itself.
- β Build Tools to Build Tools β Never reinvent what's already good and available. (The proverb "Don't write a Lisp in C++" is the same warning: don't implement an interpreter when you're writing an app.)
- β Hiding Complexity β "The complexity of the task should be in the data, not in the code." (Rob Pike.) The hallmark of great tools: it looks obvious in retrospect.
- β Make the Reader a Writer β Tools whose input format is easy to produce. This is why ed, awk, and JSON won over baroque alternatives.
- π¬ "Debugging is twice as hard as writing a program in the first place. Therefore, if you write the program as cleverly as possible, you can't debug it." (Kernighan & Plauger.)
- π¬ "When in doubt, use K&R braces." β Their actual term was "K&R" (Kernighan & Ritchie) and the advice was "use consistent style." Braces, indentation, naming: consistency beats correctness of style choice.
- π¬ "There are only two hard things in computer science: cache invalidation and naming things." (Phil Karlton; the first, "off by one errors.")
- β Pure Functions β Same input, same output, no side effects. Trivially testable, trivially parallelizable, trivially cacheable, trivially reasonable.
- β
Immutability β Reassignment is a bug source; produce new values. (Haskell, Clojure, Elixir, Kotlin's
val, Rust's non-mutbindings.) - β Referential Transparency β An expression can be replaced by its value without changing behavior. The formalization of "no hidden state," and the compiler's strongest correctness tool.
- βοΈ Side Effects at the Edges β Functional core, imperative shell. Push I/O to the boundary so the middle is testable. (Alan Kay's "The Meaning of Object-Oriented Programming", 1998.) This is the pragmatic synthesis almost everyone lands on.
- βοΈ Monads β A composition mechanism for computations with effect. Monads are a monoid in the category of endofunctors, yes, and also a way to thread state/errors/IO through pure code. In practice: learn them from TypeScript/Python/Haskell basics (Option, Result, flatMap) before the category theory.
- β
Declarative Over Imperative β What you want, not how to get it. SQL, HTML, CSS, React, SQLAlchemy, Terraform,
.map/.filter/.reduce. So what: the interpreter's job is to pick a good plan, not just follow your instructions. - β Composition β Build from small pieces that combine. The functional answer to the Unix answer β same philosophy, different substrate.
- β "Elegant is a Preference, Correctness is a Requirement." β Rob Pike's framing. Optimizing for elegance means optimizing for few concepts and low surprise. Do it.
- β
Total Functions / No Hidden Failure β Make partiality explicit:
Option,Result,Result<Vec<T>>. (Rust's community ruleset and Clippy lints are the practical enforcement of this.) - π Lazy Evaluation / Streams β Don't compute what you don't need. Guard against leaks/strictness surprises.
- π Algebraic Data Types β Model a domain as
type Shape = Circle(Radius) | Rect(Width, Height). This is the formal root of "make illegal states unrepresentable." - π Pattern Matching β Exhaustive matching on data shape, forcing the compiler to flag missing cases. The most underrated language feature for correctness.
- β Encapsulation / Information Hiding β Objects should hide their representation and invariants. (Bertrand Meyer, "Object-Oriented Software Construction".)
- β Objects Are Values With Behavior Attached β Alan Kay's framing of OOP: not C-with-classes, but message-passing small objects with private state. The interesting reading, and the one that influenced Smalltalk and the design of the web.
- β Combine Data and Behavior β Behavior that needs to know a data's invariants lives with the data. Otherwise invariants leak and rot.
- βοΈ Design by Contract β Preconditions, postconditions, invariants checked at module boundaries. (Bertrand Meyer, Eiffel.) Practical status: underused, in part because run-time checks cost, and in part because you can get much of the value from types, tests, and assertions.
- βοΈ "Program to an Interface" (Gang of Four, preface) β See Part I.
- π ModelβViewβController / ModelβViewβPresenter β Separate the domain from its presentation. (Trygve Reenskaug, 1979.) Arguably the most consequential MVC formulation.
- π¬ "The best OO design is a hierarchy of plain types with behavior attached, not a class diagram."
The hard truths, largely from Werner Vogels' ACM Queue "Life Beyond Distributed Transactions" (2012) and Designing Data-Intensive Applications (Kleppmann, 2017). Read Kleppmann; it's the best single source in software engineering right now.
- β The Fallacies of Distributed Computing β Classic traps: network is reliable; latency is zero; bandwidth is infinite; network is secure; topology doesn't change; there's one administrator; transport cost is zero; the network is homogeneous. (Peter Deutsch, "The Fallacies of Distributed Computing," 1994 β he originally listed four; the canonical eight-item form is a later expansion popularized by Sun Microsystems and Jim Gray.) You will hit every single one.
- β There Is No "Exactly Once" β Over a network, you get at-least-once or at-most-once. "Exactly-once" is an illusion, achievable only in a narrow scope (Kafka's transactional per-partition processing) and only end-to-end with idempotent consumers. (Kleppmann, "Please stop calling databases CP or AP"; also "It's a Lie!").
- β Partitions Are Not Failures, They're Normal β The network will partition. Design for it as the ordinary case, not the exception. (CAP: the choice isn't "consistency or availability" once partitions are unavoidable β it's between CP and AP during a partition. Design for the partition.)
- β Make Operations Idempotent β Because retries are inevitable. Idempotency keys, dedup, UPSERT, versioned messages.
- β Idempotence and Ordering Are Separate Concerns β At-least-once delivery + idempotent handlers + explicit ordering guarantees per key is the common, correct pattern.
- β Consistency and Latency Are a Tradeoff β Linearizability costs you a round trip. A good system makes the tradeoff explicit, per operation, and consistent with the product.
- β Clocks Are Lies β Never use distributed wall-clock for ordering. Use logical clocks (Lamport), version vectors, or a monotonic sequence per entity. (Kleppmann ch. 9.)
- β Concurrency Bugs Are Design Bugs β "Heisenbugs" from shared mutable state don't get fixed by better testing; they get fixed by a design that makes the illegal interleaving unrepresentable. (C.A.R. Hoare, "Hints for Computer System Design," 1969.)
- βοΈ Eventual Consistency Is Fine, Hidden Eventual Consistency Is Not β Async propagation is fine. Being surprised by it is not. Put the eventual bit in the design doc, in the API contract, and in the user-facing behavior. (This is the rule, and it prevents the other 20% of the argument.)
- β Bounded Queues and Backpressure Are Features β Unbounded queues convert a spike into an OOM, minutes later. Choose your bound on purpose.
- β Prefer Immutable Messages / Append-Only Logs β History you can replay, audit you didn't have to build, recovery you can test. (Kafka, event log, append-only storage.)
- β No Distributed Transactions β Use saga patterns or, simpler, design so distributed atomicity isn't required. (Vogels' central claim.) "Two generals" / "atomic commitment" show the impossibility; embrace it.
- β Timeouts Must Be Set Explicitly, Everywhere β And be pessimistic. An unset timeout is a hang; an optimistic timeout is a flapping circuit breaker.
- β Bounded Retries with Exponential Backoff and Jitter β And a dead-letter path when retries are exhausted. Silent message loss is worse than a visible failure.
- β Prefer Crash-Only / Stateless Designs Where Possible β A process that holds no state can be killed and restarted at any moment, and it's dramatically simpler. (Vanity Fair's "You Can't Win, You Can't Lose" as crash-only software.)
- β The Tail Is the Hard Part β At scale, p99 dominates your users' experience and your on-call rotation. Design for the tail explicitly; it's often 100Γ the median's work. (Dean & Barroso, "The Tail at Scale.")
- π¬ "Amateurs talk about complexity. Experts talk about the failure modes." (Kent Beck.)
- π¬ "Design for the failure you will have, not the one you expect."
- β Data Structure + Algorithm = Program β Niklaus Wirth, Algorithms + Data Structures = Programs (1976). Most of what we call "framework knowledge" is really just familiarity with standard data structures.
- β The Right Data Structure Changes the Complexity β Choosing an index, a denormalized read model, or a trie can turn O(n) into O(1) without a line of clever code. The best optimization is often a better data structure.
- β Move Computation to the Data β Databases, query engines, and vectorized runtimes are fast because they process data in bulk, close to it. So should your code. (Jim Gray.)
- β Index Everything You Query, Nothing You Don't β The index is a data structure you pay write-time for. Make it deliberate.
- β Store Denormalized, Compute Normalized β Read models optimized for the query; sources of truth kept canonical. The CQRS idea at its honest, minimal form.
- β Schema Evolution Is Inevitable β Design for additive, backward-compatible change: nullable-then-backfill-then-required. Never a big-bang migration. (Martin Fowler, "Evolutionary Database Design"; "Parallel Change".)
- βοΈ Event Sourcing β See Patterns People Misuse. Right when audit and temporal query are the product; wrong when you just wanted "a log."
- π CQRS β See above. Genuine when read and write shapes diverge; a doubling of code if they don't.
- π Materialized Views / Derived State β Precompute for reads, and own the refresh path explicitly.
- π The Log as the Source of Truth β Replay is the ultimate "reproducible bug report."
The same principles express differently per language. This section is where the abstraction meets the metal.
- β K&R / C89 β C99 β C11 β C23 β Know which standard you're targeting; the idioms differ.
- π Declarations in
forLoops β (C99.) The idiomatic form:for (size_t i = 0; i < n; i++)rather than predeclaring. - π
staticin a Translation Unit β File-local linkage. Encapsulation at the module level; the C answer to "no God object." - π
constEverywhere β Tells the reader (and the compiler) "this doesn't change."const char *vs.char * constvs.const char * constis worth memorizing. - π Single-Definition Rule (C) / ODR (C++) β One definition per symbol, program-wide. Violations are ODR violations (C++) or flat-out undefined behavior (C).
- π Rule of Zero / Rule of Five / Rule of Three β If you define a destructor, copy, move, or
operator new/delete, understand which of the three you need. (C++ Core Guidelines: "R.0: Define special member functions only when you need to.") Prefer Rule of Zero: usestd::vector/std::stringand write none of them. - π
std::string_view/string_spanβ Non-owning, cheap, zero-copy string parameters. (C++17.) - π RAII β Resource Acquisition Is Initialization. Every resource wrapped in a type whose destructor cleans it up. This is why C++ has no
finallyand needs none, and whywith/usingin other languages is the same idea. - π Move Semantics β Transfer ownership cheaply, leave the source in a valid-but-unspecified state. (C++11.)
- π Prefer Composition to Inheritance β Public inheritance is a promise you can't take back. (Effective Java Item 18; C++ Core Guidelines C.35.)
- π Exception Safety Levels β Basic, strong, and nothrow guarantees. If you're unsure which you're providing, you're providing the first. (C++ Core Guidelines E.6.)
- βοΈ Exceptions vs. noexcept vs. Errors β Throw for exceptional conditions; use
noexceptin destructors and moves; return status codes in hot paths if you must. (John Lakos, "Large-Scale C++".) - π The Rule of Least Surprise for
deleteβ Matchnewwithdelete/delete[]correctly; the classic C/C++ bug. (C++17'sstd::unique_ptrlargely fixes this.) - π¬ "In C++, there are two kinds of code: the code that has RAII and safe objects, and the code that doesn't."
Kent C. Condie's Effective Go, the Code Review Comments, and Rob Pike's talks. The language's idioms are unusually consistent because the standard library and reviewers enforce them.
- β "Accept interfaces, return structs" β Go Proverbs. Interfaces belong to the caller, not the implementer. Define the interface where it's consumed.
- β "The bigger the interface, the weaker the abstraction" β Rob Pike. One or two methods is a good interface. "Make the method count."
- β Clear is Better than Clever β Go Proverbs. If it needs a comment to explain, make it simpler.
- β Don't Repeat Yourself, but "A little copying is better than a little dependency" β Copy a small function rather than build an abstraction that couples two things that will diverge.
- β Getters Should Be Omitted β Accessors add noise, hide invariants, and invite scattering. Export fields or provide methods when behavior is actually needed. (Effective Go.)
- β
Mixed Caps for Multiword Names, Not Underscores β
MaxLength, notMax_Length. - β
io.Reader/io.Writer/io.ReaderFromβ "Small interfaces make code composable" made concrete. Go's standard library is the proof: tiny one-method interfaces compose into an enormous ecosystem of consumers (bufio, gzip, crypto, HTTP) without any of them knowing about the others. The design rule: an interface with one method is nearly always right;io.Fouris a warning. - β
Errors Are Values β Go Proverbs. Errors are ordinary values: compare with
errors.Is/errors.As, wrap with%w, handle them early. This is a design decision, not a limitation β it means errors flow as data. - β Panic for Truly Exceptional, Error for Everything Else β Go Proverbs. Panics are for programming errors and unrecoverable states, not flow control.
- β
deferImmediately After the Error Check β Record the intent next to the acquisition. Also whydeferis the basis of Go's resource management (no RAII needed). - β Zero Value Useful β Types should be usable as declared. This is a real design constraint: it means no "constructor required for a valid value."
- π¬ "If it's worth doing, it's worth testing." β Rob Pike. (Frequently misquoted as "worth doing badly," which inverts the meaning entirely. The original Gopherfest talk is a call to add tests, not to lower the bar.)
- π Zero-Value-Nice API & Constructors β
sync.Mutex{}andsync.Once{}are usable withoutNew. Ask: does your type satisfy this? - π Errors Wrapped with
%wβ The structured way to carry context and inspect the chain. (Go 1.13.) - π Functional Options for Constructors β
func NewServer(addr string, opts ...Option) *Serverinstead of a config struct with 15 nullable fields. This is the Go answer to the builder pattern. - π Channels as Queues,
context.Contextfor Cancellation βctxis the standard way to pass deadlines and cancellation; it flows with the call graph.selectwithctx.Done(). - βοΈ Channels vs. Mutexes β Channels for communication and ownership transfer;
sync.Mutexfor protecting a struct's internal state. Rule of thumb: "don't communicate by sharing memory; share memory by communicating" (the Go proverb) is aspirational, not a law. - π
go+sync.WaitGroupfor Concurrency βerrgroupfor concurrency with error propagation. Sane defaults: Goroutines are cheap, but goroutine leaks are still real bugs; every spawned goroutine needs a guaranteed exit. - π Table-Driven Tests β The canonical Go testing idiom.
for _, tt := range tests { t.Run(tt.name, ...) }. Names your cases; the subtests pay off forever. - π Variadic Options / Embedding for Composition β Anonymous struct embedding for method reuse; remember it also promotes fields, which is sometimes surprising.
- βοΈ
interface{}βanyβ generics βanyis an alias forinterface{}(Go 1.18) for readability. Generics added real value for slices/maps/containers without erasure; a good example of a language growing carefully. - π¬ "Clear is better than clever." β Go Proverbs.
- π¬ "Errors are values." β Go Proverbs.
- π¬ "Don't communicate by sharing memory; share memory by communicating." β Go Proverbs.
- β Ownership, Borrowing, Lifetimes β The type system enforces at compile time that data is uniquely owned or shared immutably, never mutated while shared. This is a whole class of data races and use-after-free removed by construction.
- β The Type System Is Your Enemy's Enemy β It's fine to be "fighting the borrow checker." It's also how you get guarantees that would take a runtime lock in every other language.
- β
Make Illegal States Unrepresentable β Rust's flagship application.
Option<T>,Result<T, E>, newtypes, typestate. - β
No
unsafeWithout a Documented Invariant β Rust API Guidelines, F.1βF.2. Unsafe is a contract with your future self, written down. - β
?Operator for Error Propagation β Propagate and convert in one character. Idiomatic Rust returnsResultfrom almost every fallible function, and most functions are fallible. - β
unwrap()/expect()Only When Failure Is a Programming Bug β Not for "should never happen" at runtime with external input. Clippy'sunwrap_usedlint and theexpectmessage convention are the standard. - β
panic!for Unrecoverable,Resultfor Recoverable β Same spirit as Go, different spelling. - β
Newtype for Type Safety β
struct UserId(u64)won't pass whereOrderIdis expected. A zero-cost type-state machine. - π Trait as the Interface β Traits are the interface, but the generic form (
fn f<T: MyTrait>) is preferred for static dispatch;dyn Traitonly when you need heterogeneity. (The "trait objects vs. generics" rule of thumb: generics for performance,dynfor type erasure.) - π
impl Traitand RPITIT β Return types you don't want to name. Getting much better ergonomics. - π Iterators and Zero-Cost Abstractions β The core of Rust's design philosophy: expressive code, compiled away. Admit honestly: iterator chains can hurt compile times and debuggability; the Rust Book now explicitly teaches
forloops first. - π
cargoEcosystem βcargo clippy(the linter that teaches idioms),cargo fmt,cargo test, and the ubiquitousResultconvention. Clippy is arguably the best code-teaching tool in any language. - βοΈ Async Rust β Powerful and still stabilizing (poll-based,
Sendbounds, executor choices). Honest status: the borrow checker meetsasyncin genuinely hard places; the ecosystem is converging but the ergonomics are not fully settled. - βοΈ Macro Hygiene β Macros can express a lot; they're also where Rust readability problems live. Pragmatic: prefer functions and generics, reach for macros for DSLs and truly repetitive code.
- π¬ "Make illegal states unrepresentable." β the Rust community's motto, and it's the most portable design lesson here.
- π¬ "No unsafe without a soundness argument."
- β Explicit is Better than Implicit β Zen of Python, item 2 (PEP 20). Type hints, keyword-only args, no cleverness.
- β Readability Counts β Zen, item 7. Code is read far more often than written. Optimize for the reader.
- β There Should Be One Obvious Way to Do It β Zen, item 12. Not always achievable, but when you break it (e.g., five different idioms for the same list operation), you've made a mess.
- β The Zen of Python, item by item β 19 aphorisms, arguably the best single-page design philosophy in any language. Print it.
- β
Type Hints for Boundaries β Especially at module edges and in public APIs. Python 3.5+;
typingmatured a lot, andmypy/pyrightin CI pay off. - βοΈ The GIL and Its Retirement β CPython's Global Interpreter Lock serializes bytecode execution within one process, so plain threads don't give CPU parallelism. The removal is a three-phase, multi-year project, and the phase numbers matter:
- Phase I (Python 3.13, Oct 2024) β free-threaded build available but explicitly experimental (PEP 703).
- Phase II (Python 3.14, Oct 2025) β free-threaded build is officially supported, no longer experimental, but still opt-in, not the default (PEP 779, accepted June 2025).
- Phase III (undecided) β free-threading as the default. Explicitly deferred to a future PEP.
- Tradeoffs to know before you commit: roughly 5β10% single-thread overhead, meaningful multi-thread speedup on CPU-bound work, and ~15β20% higher memory use.
- The lesson, independent of CPython's roadmap: performance characteristics are architecture decisions, not language trivia. And the ecosystem strategy is "let C do the loops" β NumPy and friends release the GIL and are fast anyway.
- π Duck Typing β "If it walks like a duck..." β implicit interfaces via behavior, enabled by PEP 484 /
typing.Protocol.Protocolis the modern, explicit form of duck typing and a very good idea. - π Context Managers (
with) β Deterministic cleanup, no exceptions, works for anything with__enter__/__exit__.contextlib.contextmanagerfor custom ones. - π Decorators β Functions that wrap functions. Order of operations is
@app.routebelow@app.route. Useful and easy to overuse; a nested closure is often clearer. - π Comprehensions β Idiomatic data transformation: list/set/dict comprehensions, and the generator expression for lazy evaluation. (Readability note: very long comprehensions are usually a sign to extract a function.)
- π The GIL-Releasing Ecosystem β NumPy, and the wider "let C do the loops" pattern. Design lesson: know where your code's actual time goes.
- π
__slots__β Memory-efficient instances by banning__dict__. The idiomatic answer to Python's memory overhead for many small objects. - βοΈ Namedtuples vs. dataclasses vs. attrs vs. Pydantic β
dataclassis the default for structured data (stdlib, clean, typed). Namedtuples when you want tuple behavior/tuples for compatibility.attrs/Pydanticfor validation and more. Don't over-engineer a small record. - βοΈ Inheritance vs. Composition in Python β Python's
duck typingmakes composition usually simpler; avoid deep multiple-inheritance hierarchies. - π¬ "Simple is better than complex." β Zen, item 3.
- π¬ "Beautiful is better than ugly." β Zen, item 1. When a design is clean, it tells you the right way to use it. (Tim Peters.)
- β The Two-Problem Family of JavaScript β (Doug Crockford.) ASI and type coercion. The solution, historically, was "be careful and use a linter." The modern solution: TypeScript.
- β
Strong Typing Is Non-Negotiable Now β In practice: TypeScript's cost is low (gradual adoption, JSX-compatible) and its value is high (documented interfaces, safer refactors, editor autocomplete). The idiom is strict mode + no
anyin public APIs + a lint rule banning it. - β
unknownOveranyβunknownforces you to narrow;anyopts out. Rule of thumb: if you needany, you haven't figured out the type yet. - β Discriminated Unions for State β Model mutually exclusive states as a union with a literal discriminant field. TypeScript's narrowing then makes illegal states unrepresentable. This is a genuinely great design idea and the most valuable TS idiom.
- β
Narrowing,
satisfies, and Type Guards β Teach the compiler what it can't infer.satisfies(4.9+) lets you validate a shape while keeping its precise type, which is a real ergonomic win overas. - π Immutability for React & State β
const+ spread; immutability libraries for deep structures. Why: reference equality drives change detection and cheap memoization. - π Functional Core, Imperative Shell β Keep React components about rendering; put effects/logic in plain functions and hooks. Test the pure parts.
- π Promise Discipline β
async/await; handle rejection; avoid promise pyramids;Promise.allfor parallelism; never mixawaitin a loop by accident (sequential, not parallel). Unhandled rejections are the #1 runtime bug. - βοΈ ES Modules vs. CommonJS β ESM is the standard; respect the interop pain during transition.
- βοΈ Loose Equality (
==) vs. Strict (===) β Crockford's rule: always===. Automatic type coercion in==is a rich source of0 == "0"bugs. (Alsonullvs.undefinedvs. missing: know which one your API returns.) - βοΈ
undefinedvs.nullβundefined= absent/unset;null= explicitly empty. Be consistent; ESLint rules help. Mixing them is a reliable source of confusion. - βοΈ The Prototype Chain and
thisBinding βthisis determined lexically by how a function is called, not where it's defined.call/apply/bindfix it. The honest advice: use arrow functions for callbacks, regular functions for methods, and.bindin constructors.* - π Event Loop and Microtasks β
setTimeout(macrotask) vs.Promise.then(microtask) ordering; the microtask queue drains before the next macrotask. This explains most "why did that log out of order" bugs. - π
package.json&node_modulesSemVer β Caret (^) vs. tilde (~) ranges; lockfiles are the source of truth for reproducibility. "It works locally" is a dependency-resolution problem. - π¬ "There are only two hard things in computer science: cache invalidation, naming things, and off-by-one errors."
- π¬ "Simplicity is the ultimate sophistication." β Design principle, not a JS-specific truth.
- β Composition Over Inheritance β (Effective Java, Item 18.) Java's single class inheritance plus deep hierarchies of implementations of interfaces is a trap. Prefer composition.
- β "Favor Composition Over Inheritance" and "Program to Interfaces, Not Implementations" β (Effective Java, Items 18 and 64.) Still the two highest-value items.
- β
finalClasses andObjects.requireNonNullβ Make your invariants enforced by the compiler and constructor. (Joshua Bloch's advice to make classes final by default.) - βοΈ Checked Exceptions β The classic debate. Bloch says they're a mistake for the most part (differently declared = duplication, lambda-hostile); others defend them for recoverable conditions. Pragmatic: Java APIs you write today lean toward unchecked, documented exceptions.
- βοΈ Null β Tony Hoare's "billion-dollar mistake" (2009) admits null was his idea. (Sir Tony's Null Reference β International Conference on Communication Technology, 1986.) Modern mitigation:
Optional(poorly understood, not a field type), nullability annotations (JSpecify β still evolving),@NonNulltooling, or Kotlin. - β
Generics and Type Erasure β Java's type safety is compile-time only; the runtime sees raw objects, which is why
new T[]needs an unchecked cast and why unchecked operations are a warning rather than a compile error.β οΈ Correction to a common claim: C# shares Java's erasure model β it is not reified (only value types get partial runtime type identity, andtypeofbehaves differently). Kotlin and Scala are the JVM languages that reify generics. Design lesson: don't build APIs around runtime type introspection; a type parameter is a compile-time convenience, not a runtime capability. - βοΈ Records and Sealed Classes β Java 16/17 brought algebraic-data-type-flavored modeling:
recordfor data,sealed+ pattern matching (Java 21) for exhaustive state. This is the "make illegal states unrepresentable" lesson arriving in Java. - π The JVM's Real Advantage: GC, JIT, and Portability β Write once, run anywhere, plus extremely strong runtime optimization. The cost: startup time, memory footprint, and a ceiling on some deployment models (though GraalVM native image is closing that).
- π Streams β Declarative data processing, and a real "declarative over imperative" idiom. Beware:
parallelStreamis a footgun, and streams can be less readable than a plain loop for simple cases. (Joshua Bloch: "Don't use streams for side effects.") - π Immutability β Effectively final,
List.copyOf,record,String. Java's mainstream best practice; the safest code to share across threads. - π¬ "When in doubt, favor composition over inheritance." (Effective Java.)
- β The Database Is Not Your application's Storage Layer β It is your application β The first rule of a serious system: the model is the product. Modern ORMs mean the schema shapes your product's capabilities.
- β You Shapes, It Forms β The best architectures start with the data model, not the endpoints. (Richardson.) Get the schema right; the rest is easier than you think.
- β
Prefer Explicit Over Implicit β Explicit
JOINs, explicitUNION(vs.OR), explicit transaction boundaries. Rule of thumb: SQL's implicit behaviors (type coercion, NULL three-valued logic, implicit casts) are where the "weird bug" lives. - β
Learn NULL's Three-Valued Logic β
NULL = NULLisNULL, notTRUE.NOT INwith a NULL in the subquery returns no rows. This single fact explains most surprising SQL. - β
Index What You Query β A B-tree index, a covering index, a partial index, a composite index in the right column order. Rule: a composite index
(a, b)servesaand(a,b)but notb. - βοΈ Normalization (1NFβ3NF/BCNF) β Normalize until it hurts, denormalize until it works. Joins are cheap; wrong data isn't.
- β The Two-Phase Commit Is Usually the Wrong Answer β Distributed transactions in the application database are avoided for good reason; prefer local transactions + an outbox pattern or eventual consistency.
- β
Batch Your Writes β One
INSERTwith 10,000 rows beats 10,000 round trips. Batching is the single biggest database performance lever. - βοΈ ORM vs. Query Builder vs. Raw SQL β Raw/composable SQL (e.g.
sqlc,kysely,drizzle) keeps SQL as SQL while getting type safety. Often the modern sweet spot. Tradeoff: raw SQL requires real SQL skill; that's not a downside. - β
ACID vs. BASE, and CAP, Correctly β CAP is about behavior during a network partition, not a general consistency/availability choice; partition tolerance isn't optional. Know the actual guarantees your database offers: isolation levels (Postgres
READ COMMITTEDis notSERIALIZABLE) and where it falls short. (Kleppmann, "Please Stop Calling Databases CP or AP.") - π Materialized Views β Precomputed read models, refreshed on schedule or on change.
- π Key-Value, Document, Graph, Time-Series, Vector β Each store's access pattern dictates its fit. Match the store to the access pattern and the consistency requirement, not to the hype.
- π Connection Pools β Every serverless function exhausting its pool is a classic outage. Know your pool limits and where they are.
- π¬ "It works in my database" β the SQL edition.
- π¬ "Premature optimization is the root of all evil" is about micro-optimizations; the real wins are almost always a schema or index fix. (Donald Knuth's original phrasing was about 95% of premature optimizations being misguided β the pithy quote is a simplification.)
- β
Fail Fast,
set -euo pipefailβ The first line of a script should beset -euo pipefail. Unset variables (-u) catch the classic "expands to nothing" bug. But understand it:-ehas sharp edges in loops and pipelines; use explicit error handling where it matters. - β
Quote Everything, Always β
rm "$file", neverrm $file. Unquoted variables in scripts are the #1 source of both bugs and, occasionally,rm -rf /incidents. - β
Handle Filenames with Spaces and Globs β Always
"$@"when forwarding arguments, never$*. If you must handle arbitrary input, you need care. - β
Compose, Don't Reimplement β Pipe to
jq,sed,sort,awk,xargs -0rather than reimplementing in a language you're less careful in. - β Idempotent, Declarative Automation β Idempotent scripts can be re-run safely. Use: declarative tools (Terraform, Ansible, Nix, package manifests) over long imperative scripts where you can; they'll be readable and re-runnable.
- π Shebangs & Shell Strictness β
#!/usr/bin/env bashfor portability;#!/bin/shand POSIX-only constructs for maximum portability. - π Heredocs &
xargs -0β Null-delimited for filenames with weird characters; heredocs for readable inline blocks. - π
trapfor Cleanup β Guarantee cleanup on exit, including failure. - π¬ "Shell is a programming language, not a scripting language." β Treat scripts as code: lint them (ShellCheck), review them, quote them, test them.
How code becomes running software, and β the part everyone under-plans β how it comes back off again.
- β Rollback Is a Design Requirement, Not an Ops Task β If you can't roll back in under five minutes, the design is incomplete. "Deploying is easy, un-deploying is hard" is the correct prior. So what: rollback difficulty is a property of your schema changes and your data model, decided months earlier.
- β Expand, Migrate, Contract β Never rename or drop a column in one deploy. Expand: add the new thing, write to both, backfill. Migrate: dual-read/single-write, move readers over, verify. Contract: stop writing the old, then remove it β in a later release. (Fowler, "Parallel Change"; also the expand/contract pattern Martin Thompson popularized.) The rule: a deploy is always backward-compatible with the currently running version, because during a rollout both exist simultaneously.
- β
Feature Flags for Risk, Not for Control Flow β A flag is the right tool to separate deploying from releasing, and to enable instant kill.
β οΈ It is the wrong tool for business logic: every flag is a permanent conditional and a combinatorial testing problem. Delete the flag when the decision is made β a flag nobody removes becomes a permanentifand a permanent untested path. (The "flag rot" problem; see "The Four-Tenths Rule" in Split.io's writing for a good treatment.) - β Progressive Delivery β Canary, blue-green, or percentage rollout: ship to 1% and watch error rate, latency, and business metrics before the other 99%. So what: the question "is this change safe?" is answered empirically instead of by argument.
- β The Deploy and the Release Are Different Events β Deploying is a technical act; releasing is a business decision. Conflating them means either you're blocked on a deploy window, or you ship dark code to everyone by accident. Feature flags and staged rollouts are how you separate them.
- β Automated, Reproducible, Hermetic Builds β The artifact should be buildable from source on any machine, with pinned dependencies and a lockfile committed. "Works on my machine" is usually a dependency-resolution problem. Why it matters more than it sounds: an unreproducible build is an unauditable one, and you cannot roll back to something you cannot rebuild.
- β Same Artifact, Every Environment β Build once, promote that exact artifact through dev β staging β prod. Rebuilding per environment means what you tested is not what you shipped. (The Twelve-Factor "build, release, run" separation.)
- β Backwards Compatibility as a Discipline β Assume a client β yours, someone else's, or a background job that hasn't restarted β is still running the old code. This single assumption eliminates a whole class of 3am failures.
- βοΈ Semantic Versioning β
MAJOR.MINOR.PATCHwith real meaning: break the API β major, add backward-compatible functionality β minor, fix only β patch. Practical status: widely claimed, routinely ignored, and it only works if consumers trust it. A major version of 0.x is a free pass; a version 1.0 is a promise. (Tom Preston-Werner.) - π Blue-Green β Two identical environments, switch traffic atomically. Instant rollback, at the cost of double capacity and the classic trap of non-idempotent production side effects.
- π Zero-Downtime Migrations β All schema changes must be compatible with both versions simultaneously. Add-then-remove beats drop-then-add, always.
- π Configuration Is Not Deployment β Config changes are riskier than code changes because they bypass review, tests, and often audit. Treat them as production changes: versioned, reviewed, and observable.
- π Release Trains and Scheduled Releases β Batch many changes on a fixed cadence (weekly/monthly) rather than releasing whenever a feature finishes. Why it's good: limited blast radius, predictable on-call, and far fewer integration surprises. Why it's contested: it adds delay for the team that finished early.
- π Database Migrations Are Deployments β A schema change that breaks a rollback is not a reversible change. This is the single most common reason a "safe" deploy becomes an incident requiring a restore.
- π¬ "You can't deploy without a rollback plan." β If the plan is "restore from backup," your RTO is measured in hours and you've already lost the data written since.
The system is running. Now the work changes character: you are no longer writing software, you are running it and learning what you didn't know.
- β Observability > Monitoring β Monitoring asks known questions ("is the CPU high?"). Observability lets you ask novel questions about a system you didn't anticipate failures for. Three pillars: metrics, logs, traces. (Charity Majors' argument, and the OpenTelemetry standard that is the practical convergence of all three.) The design test: can you debug a novel failure without reproducing it first?
- β Instrument for the Question You Haven't Asked Yet β Log the things that, if they went wrong, you'd need to know and wouldn't have. Correlation IDs, actor, resource, and outcome on every state transition. Why: the one log line you didn't write is the one that ends the investigation.
β οΈ Cardinality Is a Budget β Every unique label combination is a new time series. Auser_idorrequest_idlabel will melt your metrics backend. Metrics: bounded cardinality. Traces/logs: unbounded. Getting this backwards is the most common self-inflicted observability outage. (Same for the logs equivalent: unbounded log volume has a real dollar cost.)- β Measure the User, Not the Machine β Request rate, error rate, and duration (the RED method), or availability, latency, and throughput (the SLI set). CPU, memory, and GC are diagnostics β they help you explain a symptom, they're not the thing users experience. (Google SRE Workbook ch. 5.)
- β Trace Across Service Boundaries β Distributed tracing is the only way to find the slow dependency when you have eight services. Propagate context explicitly; don't infer it from timestamps.
- β
Structured Logging Beats String Logging β Emit
key: valueor JSON, not interpolated prose. Prose logs can't be queried, filtered, or aggregated; structured logs can. (The rule: if you can't grep it, you're reading it, not querying it.) - β Redundancy Is Not Backup β A replica is not a backup. Test your restore. The most expensive finding in any postmortem is "we had backups we had never restored." (The 3-2-1 rule: 3 copies, 2 media types, 1 offsite. And verify the third is genuinely offsite β same-account backups are not a plan.)
- β Define RTO and RPO Before You Need Them β RTO: how long can you be down? RPO: how much data can you lose? These two numbers determine every architecture decision about replication and backups, and they come from the business, not engineering. So what: "we didn't think about it until we needed it" is the answer that costs the most.
- βοΈ Active-Active vs. Active-Passive β Active-active gives lower RTO and much higher complexity (conflict resolution, divergent writes). Active-passive is simpler and has a real failover step that must be tested. Choose on RTO/RPO, not on aspiration.
- β Capacity Planning From Trends, Not Spikes β Project from the trend line and the known roadmap, not last month's peak. Include the headroom for the growth you've already committed to, and remember that autoscaling solves throughput, not per-request cost or downstream saturation.
- β Graceful Degradation β When a dependency fails, shed load rather than collapse. Return cached or partial results, queue for later, disable the expensive feature. Why: total unavailability is a worse user experience than a partial one, and partial failure is much easier to design for than full.
- β The Runbook Must Be Executable by a Tired Human at 3am β Include the exact commands, the exact dashboard links, and the "if this, then that" decision points. A runbook that says "investigate the issue" is not a runbook.
- β You Build It, You Run It β Shared responsibility for operations, not a separate "ops" team downstream. (Vogels.) Teams that ship but never operate make different mistakes, and the two feedback loops are the point. The staffing and rotation consequences live in Part X.
- β Blameless Postmortems β When a system fails, the human is usually working with the information the system gave them (or didn't). Blame stops reporting; blameless postmortems find the real cause. (John Allspaw, 2012; SRE Book ch. 1.) Write them for the next incident, not the last one.
- β Error Budgets β SLOs give you a budget; when you're within it, ship; when you're over it, fix reliability. This turns "reliability vs. features" from an argument into arithmetic. (Google SRE.)
- β Alert on Symptoms, Not Causes β Alert on what your users care about (latency, error rate), not internal causes (CPU high). CPU isn't a problem until latency is. (Google SRE Workbook ch. 5.)
- β Every Alert Should Be Actionable and Owned β If an alert needs no action, delete it. Alert fatigue trains people to ignore alerts, which is how you miss the real one.
- β Runbooks for Known Failures β The best fix for a 3am page is a step-by-step document written calmly in advance. Automate the runbook later.
- β Practice the Failure β Game days, chaos experiments, fire drills. The point isn't to find bugs, it's to find the runbook gaps and the missing permissions. An untested recovery path is a hypothesis.
- β Toil Is a Bug β Anything manual, repetitive, and automatable shouldn't be a human's permanent job. Google SRE's "eliminating toil" is the clearest framing. (The Site Reliability Book ch. 5.)
- β On-call Should Be Sustainable β Rotation limits, handoffs, and comp are real engineering constraints. A team burning out is a reliability risk, not just a wellbeing one.
- β Load-Shedding and Backpressure Are Designed, Not Discovered β Overload behavior that you didn't design, you discovered during the incident. Rate limits, concurrency caps, and queue bounds should exist and be set deliberately.
- π Health Endpoints That Actually Check Dependencies β A liveness probe that returns "OK" while the database is unreachable just moves the failure from a clear error to a mysterious hang. Liveness and readiness are different questions and need different endpoints.
- π Synthetic Transactions β A script that exercises the critical user journey from outside, on a schedule. It catches "the site is up but checkout is broken," which no internal metric will.
- π Audit Logging vs. Application Logging β Security-relevant actions (auth, permission changes, data access) need a separate, tamper-evident, retained log. Assume your application logs are not sufficient for an investigation.
- π¬ "Hope is not a strategy." β Every unproven assumption in your architecture is a page at 3am.
- π¬ "Hope is not a strategy." β Every unproven assumption in your architecture is a page at 3am.
- π¬ "It's not done until it runs in production and someone's watching."
- β Measure First, Optimize Second β Without a measurement, you're guessing, and guesses are usually wrong. Profiling tools exist; use them. (Brian Kernighan: "Unix systems used to be slow, and then people started using profilers to find the bottlenecks.")
- β Amdahl's Law β Optimizing a 5% component buys you 5%. Find out where the time actually goes first. Optimize the common path; the rare path is where latency feels bad but costs nothing in aggregate.
- β Amdahl's cousin: The Common-Case Trap β The common case deserves the most care: the cold path is where bugs hide, but it's also where a fraction of a percent of your throughput lives. Optimize the 99th-percentile path and call it done.
- βοΈ Bret Taylor's Three Questions for Performance β (1) How much faster can it be? (2) What's the cost in complexity? (3) How often is the cost paid vs. how often is the speed gained? The best performance work is usually the option that wins on all three.
- β Locality of Reference (Cache) β Modern CPUs and memory are hierarchical; locality dominates. Why: data movement costs orders of magnitude more than arithmetic β a main-memory access is roughly two orders slower than an L1 cache hit, so the "cheap" operation (a load) is often the expensive one. Optimize the layout and the data structure, not the loop. (Hennessy & Patterson, Computer Organization and Design; their CACM series on hardware consistency makes the memory-wall argument more sharply than most software books do.)
- β Amortized Analysis β Many operations cost O(1) amortized even if individual ones are O(n) (dynamic array append, hash table insert). Why it matters: it lets you design simple structures with predictable aggregate cost.
- β Caching Is the Last Resort, and a Debt β Every cache is a consistency liability: invalidation, staleness, thundering herd, and cold-start problems. Use it for genuinely expensive-to-recompute data; measure the hit rate or delete it. (See the "only two hard things" joke.)
- β Batching Amortizes Round Trips β Latency, not bandwidth, is usually the cost. Combining 1000 requests into 1 changes the game far more than making one request faster.
- β The Tail Latency Dominates the User's Experience β Dean & Barroso, "The Tail at Scale." A p99 of 2 seconds makes a p50 of 50ms feel slow. Hedge requests, avoid queueing, and measure p99 not mean.
- βοΈ Sharding vs. Partitioning vs. Replication β Sharding splits data for capacity; partitioning organizes it; replication copies for availability. Choose a partition key that keeps hot data together β the single biggest scaling decision you'll make, and the hardest to undo. (A bad partition key is the most expensive mistake in distributed systems.)
- βοΈ Synchronous vs. Asynchronous β Sync is simple and correct; async is fast and complex. Rule: make it sync by default; go async where you can prove you need the latency back.
- β Add Caches Only With a Measured Hit Rate β A cache with a 5% hit rate is pure overhead (plus invalidation bugs). Instrument before and after.
This is where software engineering becomes responsible engineering. None of the below is optional, and none is a "nice-to-have."
- β Never Trust Input β Validate and sanitize at the boundary. Parameterized queries over string concatenation. Whitelist over blocklist. (OWASP Top 10.)
- β
Never Roll Your Own Cryptography β Use audited, standard libraries (NaCl/libsodium,
bcrypt/Argon2for passwords, TLS libraries for transport). Inventing crypto is famously, reliably broken. - β Least Privilege for Every Identity β The service account, the CI token, the IAM role, the DB user. Least privilege is not just a principle, it's a containment strategy. (See "Principle of Least Privilege.")
- β Defense in Depth β Assume each layer will eventually fail; don't make one check load-bearing.
- β Fail Securely β On any error or timeout, deny by default. (The opposite of fail-open, which turns a bug into a breach.)
- β Keep Dependencies Minimal β and Patched β Every dependency is code you didn't write, running with your privileges. SBOMs, automated updates, and knowing your transitive tree are part of the job now.
- β Separate Code from Data (Injection Defense) β No string concatenation into SQL, shell, LDAP, HTML, or a template engine. Parameterize everywhere. (OWASP #1.)
- β Secrets Never in Code or Config in the Repo β Use a secret manager, environment injection, or vault. Rotate, scope, and audit. Commit history is forever; assume any secret that touched a repo is compromised.
- β Authenticate the User, Then Authorize the Action β Authentication (who) is not authorization (may they). BOLA/IDOR β checking that the request is valid but not that this user owns this object β is the most common real-world web vulnerability. (OWASP API #1.)
- βοΈ Security Is Not a Gate at the End β Retrofitting security breeds security debt and siloed reviews. Bake it into design (threat modeling, secure defaults) and into CI (SAST, dependency scanning) so it's continuous, not a phase.
- βοΈ Encrypt vs. Focus on Impact β Encryption-in-transit and at-rest are hygiene, not a plan. Prioritize by what an attacker actually wants: your auth system, your secrets, your supply chain, your business logic. A 2019 Capital One breach was an SSRF in a misconfigured WAF, not a cryptography failure.
- π Threat Modeling β "What can go wrong?" before you build, not after the incident. (STRIDE, Adam Shostack.) Cheap, and catches design-level problems no scanner will.
- π 2FA / Passkeys / Hardware Keys β Phishing-resistant authentication for anything privileged. Design principle: prefer the more secure default and let users opt down, not up.
- π Zero Trust β Never trust, always verify β per request, not per network location. Assume the network is already compromised.
- π Security Headers and Safe Defaults β CSP, Secure/HttpOnly/SameSite cookies,
frame-ancestors, proper TLS config. The best practice is the default. - π Security Logging and Incident Response β Log auth and authorization events; assume you'll need them. (You have an incident-response plan and you've rehearsed it.)
- π¬ "Amateurs hack systems, professionals hack people." β Bruce Schneier's formulation of the actual threat model.
- β The Manifesto Is a Set of Values, Not a Process β Individuals and interactions over processes and tools; working software over comprehensive documentation; collaboration over contract negotiation; responding to change over following a plan. Read the "right" column too β it says the left matters more, not that the right is worthless. (Wadler et al., 2001.)
- β The Second Half of the Manifesto Matters β "While there is value in the items on the right, we value the items on the left more." Most process disputes ignore this.
- βοΈ Scrum vs. Kanban vs. XP β Scrum is a container with sprints and ceremonies; Kanban is flow and WIP limits; XP is engineering discipline (pairing, TDD, refactoring, CI). They compose better than they compete. A great delivery process is flow (Kanban) + discipline (XP) + a cadence (Scrum's) + explicit definition of done.
- β The Definition of Done Is the Point β An explicit, shared checklist: reviewed, tested, documented, monitored, deployed, accepted by users. Without it, "done" means whatever the person who said it meant, and nobody can trust it.
- β Sustainable Pace Is a Feature β Cramming is a loan against the next sprint. Teams do not get faster by working less consistently; they get done more predictably. (Sustainable pace, iterative increment β Scrum Guide.)
- β Retrospectives β A cadence for improving how the team works, not just the work. Pointless only if the same issue keeps recurring and nothing changes.
- β XP Practices Still Hold Up β Pair programming, test-driven development, refactoring, continuous integration, simple design, collective code ownership, whole-team availability. (Kent Beck, 1999.) This is a great starting read for engineering practice independent of framework.
- βοΈ Estimating (Story Points, Velocity) β Useful for conversation about relative size, less useful as a promise. Story points measure relative, team-specific effort. Treating them as hours reliably misleads. (Never convert points to dates.)
- β The Team Should Own the Whole Thing β "You build it, you run it" (Werner Vogels). Teams that only ship don't learn what they built; on-call without shipping builds bad instincts. Not a slogan β a staffing and rotation decision. The operating detail lives in Part VII; the ownership model is here.
- β A Blameless Culture Extends Past Incidents β The posture that makes postmortems productive (Part VII) applies equally to code review, design discussion, and estimates. If people are punished for surfacing a problem, you lose the problems. If they're rewarded for surfacing one early, you get them while they're still cheap.
- β Review Latency Is a Team Health Metric β A review queue is a deployment blocker and a leading indicator that the team is over-committed. Track it like a build failure, because it degrades the same way: quietly first, then all at once.
- β The Team Should Own the Whole Thing β "You build it, you run it" (Werner Vogels). Teams that only ship don't learn what they built; on-call without shipping builds bad instincts. Not a slogan β a staffing and rotation decision.
- π Kanban WIP Limits β The most underrated practice: limiting work-in-progress surfaces the actual bottleneck faster than any retrospective.
- π Continuous Integration and Continuous Delivery β Integrate at least daily; a merge queue is a strong pattern. Deploy when ready, not in batches β small deployments are less risky, not more.
Fowler's framing, read it directly: Is High Quality Software Worth the Cost?
- β Technical Debt Is a Deliberate Shortcut β With a known reason (usually: ship now, fix later) and, crucially, with the interest rate you signed up for. Unprincipled mess is not debt.
- β
Debt Has an Interest Rate β Some shortcuts are near-free (
// TODOwith a clear fix); others compound (a bad data model, a missing abstraction, a wrong package boundary). Pay the high-interest debt first. - β "Good code is cheap; bad code is expensive" β Cost isn't just writing; it's every future change, every review, every onboarding, every incident caused by it. Refactoring is an investment, not a distraction.
- β The Debt Portfolio View β Not all debt is equal. Some is deliberate, documented, and tracked (good debt). Some is just neglect. Track it like a portfolio; prioritize by interest rate, not by what's loudest.
- β Bounded Refactoring Time β "Golden afternoon" refactoring; a culture of small, regular investment beats a heroic (and never-arriving) rewrite.
- βοΈ The Big Rewrite β Rarely succeeds. The codebase you're rewriting is the one where the requirements are least known. The second-system effect (Brooks) applies. Better: Strangler Fig β grow the new system alongside the old, migrating piece by piece. (Martin Fowler.)
- β People Are Bad at Estimating, and That's Fine β The study of judgment under uncertainty (Kahneman, Tversky). Estimates are guesses with a confidence interval. Present them that way.
- β Give Ranges, Not Points β "Two to three weeks" beats "March 15." (And the range is usually wider than feels honest; that's the calibration lesson.)
- β Timeboxing Over Deadlines β "We'll spend one week on this, then reassess" is honest. A fixed end date on uncertain work is a promise you'll break. (This is the argument for spikes: timebox learning, not build the thing.)
- β Ship in Small, Regular Slices β Smaller batches mean shorter feedback loops, less risk, and earlier correction. A big-bang release is a bet on your ability to be right for months.
- β A Deadline Is a Budget, Not a Wish β A real deadline forces tradeoffs: scope, quality, or resources. Name which one you're cutting. (Donella Meadows, The Systems Thinker.) Vague "do it by Friday" hides the tradeoff until it's too late to plan.
- β Estimation Should Include the Work Nobody Plans β Code review, integration, testing, deployment, monitoring, documentation, and the operational follow-up. Most overruns are here, not in "writing the code."
- βοΈ The Two-Week Sprint Promise β Fixed-cadence sprints with commitments are common but add pressure. Sprints as feedback cycles (not promises) work better. (Schwaber & Sutherland's later writings on empiricism echo this.)
- π Spikes β Timebox to answer a specific question, then throw away the code. Cheap way to buy certainty.
- π Proofs of Concept vs. Spikes β PoCs are "does it work at all," spikes are "how would we do it." Both are throwaway. Neither should silently become production code without a rewrite.
- β Users Don't Want Features; They Want Problems Solved β Start with the job to be done, not the feature list. (Clayton Christensen, The Innovator's Dilemma; Teresa Torres' "The Mom Test" for the interviewing discipline.)
- β Solve the Problem, Not the Requested Solution β Users are experts in their problem, not in your solution space. The first request for "a button that does X" is rarely the best answer; asking why reveals the actual job.
- β The Best Interviewer Asks About the Past, Not the Future β People are terrible at predicting their future behavior and great at recalling their last time. (The Mom Test.)
- β Ship, Measure, Iterate β Not everything is knowable before you build it. Reduce the cost of being wrong (spikes, prototypes, A/B tests, MVPs) rather than trying to eliminate being wrong.
- β The Documentation Is Part of the Product β An unauditable, unexplainable system gets abandoned, no matter how good the code is.
- βοΈ Disagree and Commit β (Intel's Andy Grove.) Once a decision is made, execute it wholeheartedly β and keep the door open for new information. Alternative, from Amazon: "disagree vigorously, then commit fully." The difference is whether you argued well, not whether you argued.
- β Nobody Is Neutral β Design, naming, defaults, and error messages shape user behavior. Assume your choices have effects; measure them. (Dan Lockhart, The Choice. Nudge theory, Thaler & Sunstein.)
- β Defaults Are Decisions β The default option is the option most people get. Choose it deliberately. (A "no" default opt-in for data sharing is stronger than an opt-out.)
- π The Cost of Switching β The best retention strategy is a product users are unhappy to leave. (Bessemer, a good lens on developer tools.)
- π¬ "Do things that don't scale." β Paul Graham, on manual, high-touch service while you find the product. Applies to internal tools and developer experience too.
- π¬ "The best way to predict the future is to invent it." β Alan Kay.
These cut across every part of the lifecycle and every language. They are easy to defer and expensive to retrofit, which is exactly why they belong in a catalog like this.
- β Design for i18n From the Start, or Never β Retrofitting i18n is a rewrite of every user-facing string, every date, every layout, and every test. The four hard requirements from day one: no string concatenation in the UI layer, no string comparison, ISO-8601/CLDR formats, and UTF-8 everywhere including the database connection. (The "pseudo-localization" technique β expanding every string ~30% and accenting characters β surfaces layout breakage before a translator ever sees it.)
- β
English Text β One String β Grammatical gender, plural rules (six forms in Russian, two in English), and word order are not edge cases in most of the world. This is why
"you have 1 item"/"you have {n} items"doesn't translate, and why you need real pluralization support, notif (n == 1). - β
Locale Is Part of the Data Model, Not a Setting β
1032.5in the US is1.032,5in Germany. Number, date, time, currency, and address formats all vary by locale, and so do legal conventions around them. Store the instant in UTC and format at the edge; store the amount as integer minor units or decimal, never a float. - β
Right-to-Left Is a Layout Problem, Not a Translation Problem β Mirroring, bidi text, and logical-vs-physical properties affect the whole UI. If you hard-coded
margin-left, you don't support RTL β and you didn't know you cared until a market required it.
- β Accessibility Is a Requirement, Not a Feature β WCAG conformance is a legal and ethical obligation in most jurisdictions (ADA, EAA, the European Accessibility Act which took effect June 2025), and it's the difference between who can use your product and who can't. So what: keyboard navigability, focus management, semantic markup, contrast ratios, and screen-reader support are the baseline, and automated checks catch roughly a third of issues β meaning the other two-thirds need manual testing with real assistive technology.
- β Test With a Keyboard, First β If you can't use it without a mouse, it's broken for a real class of users, and you've found a bug in about two minutes. This is the cheapest accessibility test there is.
- π Prefer Native Semantics Over ARIA β
<button>is focusable, announced, and keyboard-activatable for free. A<div onclick>is none of those, and ARIA is how you rebuild them badly. No ARIA is better than bad ARIA. (W3C WAI-ARIA Authoring Practices.) - π Automated Checkers Are Necessary, Not Sufficient β axe, Lighthouse, and eslint-plugin-jsx-a11y catch the machine-detectable minority. Alt text quality, logical reading order, and whether error messages make sense to a screen-reader user require a human.
- β Configuration Is Code β Config belongs in version control, reviewed like code, validated on startup, and tested. Env vars scattered through a shell history are not configuration management. (The Twelve-Factor "config in the environment" is a decent rule; the stronger version is config-as-declared-and-validated-file.)
- β Fail Fast on Bad Config β Validate all configuration at startup and refuse to boot. Discovering a typo'd env var at 2am under load is an availability incident you chose.
- π Twelve-Factor's Config Typology β Config that varies between deploys (credentials, hostnames) is env-var territory; config that doesn't (which framework to use, a routing table) belongs in code. Mixing the two produces config that's harder to reason about than either approach alone. (Adams, 2011.)
- π Feature-Specific Config Files β One file per concern, parsed strictly, errors naming the exact key. Β§Twelve-Factor.
- β Dependencies Are Code You Didn't Write β Every one runs with your privileges and can be removed or compromised. So: minimize the count, pin the versions (commit lockfiles), scan for vulnerabilities, and budget time for upgrades. So what: a transitive vulnerability in a four-year-old package is your incident, regardless of whether you ever called it directly.
- β The Upgrade Is the Work β Keeping dependencies current is not overhead; it's the alternative to a painful multi-week migration with no test coverage. Rule: continuously updated dependencies, or a scheduled quarterly upgrade budget. "We'll upgrade when we need the feature" always costs more.
- βοΈ Build vs. Buy, and the NIH Reflex β Buying is usually right for commodity, well-maintained, security-sensitive components (crypto, auth, PDF, database drivers) and wrong for your core differentiator. (Dylan Beattie's framing: build only what you'd actually sell.) The failure mode in both directions is the same β neither decision is revisited after the cost lands.
- π Vendoring / Forking as a Last Resort β Vendoring a dependency gives you control and gives away upstream fixes. It's a trade, not a free win, and it should be recorded as a decision with a review date.
- π¬ "These are all 'later' problems, and 'later' is the most expensive time." β i18n, accessibility, auth, and observability are the canonical examples of work that is cheap in week one and ruinous in year two.
- π¬ "Cross-cutting concerns are cross-cutting because they cross every layer." β The cost isn't the work, it's the number of places you have to remember to do it.
The part of the lifecycle that most catalogs skip, and the one your future self inherits. Every system gets deleted eventually; the only question is whether that's a plan or an archaeology project.
- β Sunsetting Is a Feature β A system with a known, announced, supported end date is a constrained system: you can say no to new features, delete code instead of patching it, and plan your own migration. A system with no end date accumulates features forever, each one a liability. So what: "we'll rewrite it eventually" is a plan; "we'll deprecate it on date X" is a plan you can budget for.
- β
Deprecation Needs a Policy, Not a Conversation β A real deprecation has: a published end-of-life date, a migration path, a named owner, a communication channel, and a removal ticket. (Sunset RFC process; the "three-strikes" communication pattern β announce, remind, remove.)
β οΈ The usual failure is announcing a deprecation with no date, which is just a rumor with extra steps. - β The Second-System Effect β The rewrite is the trap. The requirements are least known exactly where they're needed most, the old system encodes undocumented edge cases discovered over years, and the team's attention is split. (Brooks, The Mythical Man-Month.) Prefer incremental replacement β the Strangler Fig: build the new thing alongside, route traffic over, and delete the old capability as each piece migrates. (Fowler, "StranglerFigApplication," 2004.)
- β Migrate the Data Last β Moving the data before the reads are proven on the new system is how rewrites fail. Route reads first, verify, then move writes, then cut over. (Same expand/migrate/contract discipline, applied to an entire system.)
- β Deleting Code Is a Feature Delivered β Every deleted service is one less thing to patch, deploy, monitor, secure, and explain. Deletion is the highest-ROI work in most large codebases, and the most consistently deferred because it delivers no visible feature. So what: if your backlog is full and your system is fragile, the highest-priority ticket may be a deletion.
- β
Data Retention Is a Legal and Architectural Constraint β You cannot delete data that a regulation requires you to keep, and you must delete data a regulation requires you to erase. Both constraints shape your schema, your backups, and your migrations. So what: "delete the account" is not
DROP TABLE; it's a decision about PII in backups, logs, analytics pipelines, and third parties β a real distributed-deletion problem, not a code deletion problem. - β A Backup You Cannot Restore Is Not a Backup β Retention policies and restore procedures must be tested on the same clock as your compliance obligation. "We have seven years of retention" is a claim about a tape library, not about a working restore.
- βοΈ Refactor vs. Rewrite β Refactoring changes structure without changing behavior and is safe incrementally. A rewrite changes behavior and everything else. The decisive question is not how bad the old system is, but whether you can characterize the old system's behavior well enough to be confident the new one matches β and that characterization is usually the expensive, unglamorous part that gets skipped.
- π Kill Switches and Graceful Shutdown β Before decommissioning a dependency or a feature, know how to turn it off safely: drain connections, finish in-flight work, persist state. A shutdown that loses data turns a planned migration into an incident.
- π Zombie Systems β Systems nobody owns, nobody uses, and nobody dares delete because nobody knows who depends on them. The only cure: actively hunt them (access logs, network flows, ownership metadata) and assign an owner with a date. They are pure cost.
- π¬ "The best code is the code you deleted." β Deletion is a feature with a measurable outcome: fewer lines to maintain, fewer CVEs to patch, fewer alerts to triage.
- π¬ "No system is permanent, and the ones that are, are maintained by people who never got to leave."
- β Simplicity is Prerequisite for Reliability β Edsger W. Dijkstra. The thesis: the complexity you don't remove is complexity you can never fully verify. Reliability is a function of what you can reason about.
- β "Simplicity is the ultimate sophistication" β Leonardo da Vinci. Carry it far enough and it's a design principle: the most sophisticated solution is often the one that reduces concepts, not adds them.
- β "The best way to predict the future is to invent it" β Alan Kay. On agency through tooling.
- β "Beware of premature generalization" β Fred Brooks, The Mythical Man-Month (1975). Why it applies: generalization is a bet on future requirements; do it once, with evidence.
- β "The Mythical Man-Month" β Brooks's central lesson: adding people to a late project makes it later (Brooks's Law), because communication paths grow faster than work. Communication overhead is the hidden cost. (*Note: the second edition, "No Silver Bullet" (1986), is the better half β the essential vs. accidental complexity distinction is more useful than the first edition's scheduling content.)
- β "No Silver Bullet" (Essential vs. Accidental Complexity) β Brooks, 1986. Why it matters: some complexity is essential to the problem (inherent, hard to remove); some is accidental (tooling, process, environment). It tells you where to invest. The second essay, "No Wolves," on schedule games, is the practical corollary.
- β "The Galloping Goose" / "Galloping geese, beware" β W. Richard Stevens, UNIX Programming for the Advanced (1992). The lesson: OSes get slow in small, unexpected ways. Profile, don't guess.
- β "Premature Optimization Is the Root of All Evil" β Donald Knuth, 1971 (structured data). The real, precise version: premature optimization (before profiling) is bad; late optimization (after you know it's needed) is good. Design for the general case, then measure, then optimize the hot path. 95% of premature optimizations misguidedly ignore Knuth's own caveat; the honest version is the one above.
- β "Beware of computer scientists building models of the world" β Peter Naur, Programming as Theory Building (1985). Why it matters: the theory the programmer builds is the real system; the code is a residue. It's why onboarding is hard and why the original designer leaving is such a risk. Programming is theory building; write the theory down.
- β "Programming as Theory Building" β Same source. The deepest epistemology in the craft: understanding is not in the program text.
- β "It is easier to change the specification to fit the program than vice versa" β a cautionary (David Wheeler). The productive version: in an agile setting, treat the spec as a living contract and say when it changes, rather than silently diverging.
- β "The best way to design a system is to write the code" β Occasionally wrong, but almost always more right than designing longer in the abstract. Make the theory by typing it.
- β "The most damaging phrase in the language is 'We've always done it this way'" β Attributed to Grace Hopper; treat as attributed-but-unsourced. The positive form, which is the useful part: a convention is a hypothesis, not a law. Revisit it when the context that justified it has changed β and note that "we've always done it this way" is often a true statement being used as a non sequitur.
- β "First, solve the problem. Then, write the code" β Johnson & Johnson, quoted in the First Round CAPTCHAs review (1966). The idea predates Agile. Old ideas, newly fashionable.
- β "Do not add requirements that aren't needed now" β a form of the YAGNI principle, and the note in Brooks on "how much generality to build."
- β "Nothing can be said to be a universal best practice" β the recurring punchline of P59, the paper "The Fallacy of the Eternal Big Programmer" (Paul Woodside, Derek Rothberg, David Farley, 2019). Right: context rules. A practice is good for a goal in a context. Ask what goal, in what context, before you adopt anything. Even this list.
- β Reuse Is an Outcome, Not a Goal β The most defensible anti-reuse argument is economic, not aesthetic. Building a reusable component is several times harder than building a single-use one, and you cannot validate generality against an unknown future. Reuse that shows up in a healthy codebase is almost always a side effect of writing concise, well-scoped code for a concrete problem β not a result of designing for reuse up front. (Robert Glass, Facts and Fallacies of Software Engineering; Jeff Atwood, "The Delusion of Reuse and the Rule of Three," 2004; Uwe Friedrichsen, "The Reusability Fallacy," 2020. Friedrichsen's sharpest point: software's production cost is already near-zero, so the physical-world "assemble from parts" economics don't transfer.)
- β "Software is a gas" β Brian Kernighan. (Attributed to Bill Daley at Bell Labs, popularized by Kernighan, and now primarily a slogan rather than a sourced claim β included for the idea, not the citation.) The lesson: it expands to fill whatever space you give it β which is exactly why boundaries, ownership, and explicit constraints are the job.
- β "Design is not just what it looks like and feels like. Design is how it works." β Steve Jobs, paraphrasing Dieter Rams' 10 Principles of Good Design. Rams' tenth principle: "Good design is as little design as possible." Less, but better.
- β "I have not failed. I've just found 10,000 ways that won't work." β Thomas Edison, on invention. The application to software: most code you write should be cheap to throw away. Prototypes, spikes, and experiments exist so that the 9,999 aren't part of production.
- β "Make it work, make it right, make it fast" β Kent Beck, on TDD's red-green-reflect cycle, extended from Hoare. The order matters: correctness before speed. "Make it fast" last, because premature speed often ruins the first two.
- β "Refactoring is a form of engineering hygiene" β Kent Beck.
- β "Design Patterns: Elements of Reusable Object-Oriented Software" (the GoF book, 1994) β "A pattern describes a problem which occurs over and over again in our environment." The point is a vocabulary, not a template.
- β "The Zen of Python" β Tim Peters, 19 aphorisms. Arguably the highest signal-per-line philosophy in the language ecosystem.
Rules that sound wise and are actually how projects fail. Knowing these is as valuable as knowing the principles β they are the failure modes of over-applying the good ideas above.
- βοΈ "Real programmers don't use comments" β An overcorrection. The right version: the code should be self-explanatory; comments explain the why the code can't show. Blanket avoidance of comments isn't purity, it's losing information.
- βοΈ "Never write comments" β Same problem. The absence of a "why" comment is information loss disguised as virtue.
- βοΈ "No premature optimization" used to justify no design β Not the same as not optimizing early. Designing clean, cohesive boundaries is early design, and it's cheap. Premature optimization is about micro-optimizations before measurement, not about skipping design.
- βοΈ "Move fast and break things" β Meta's slogan, widely regretted (a real outage in 2012 was caused by a bad release). The correction: "move fast, and be responsible." Speed without safety is just a debt with a countdown. (Now Meta's own mantra.)
- βοΈ "10x engineers" β A productivity-myth that survived from a well-intentioned blog post. It measures the wrong thing and encourages heroics over systems. The right question is "how do we make the whole team faster?"
- βοΈ "Velocity is a measure of productivity" β Output metrics (story points, lines of code, commits) reliably incentivize the wrong things (gaming, churn, tiny commits). The right measures are outcomes: defect rate, time-to-value, user impact.
- βοΈ "Code coverage of 80% is a quality target" β Coverage is a floor, not a goal. Teams that chase 80% write shallow, assertion-free tests. Measure what could hurt, not what the tool counts.
- βοΈ "More microservices = more scalable" β Often the opposite until you have the teams, the tooling, and the operational maturity. Microservices trade code organization problems for distributed-systems problems.
- βοΈ "Technical debt is a failure of discipline" β Sometimes true, often not. Often debt is a rational response to a real deadline with no slack. Understand the rate and the reason before judging.
- βοΈ "There's no place for 'it works on my machine' in a professional team" β as an excuse. As a symptom, it's a real signal: your environment is non-hermetic, your dependencies are unpinned, or your setup is undocumented. Fix the environment, not the person.
- βοΈ "Agile means no planning" β Agile is the opposite: it's better planning, continuously, based on real feedback instead of a stale forecast. "Agile" as an excuse to avoid thinking has nothing to do with the Manifesto.
- βοΈ "Document everything" β Documentation that rots is worse than none: it misleads. Document the important, durable things (the why), and link rather than duplicate.
- β
Read the Code β The best way to learn a system is to read its source and history.
git logon a file tells you the story; the code alone doesn't. - β The Documentation Is a Starting Point, Not a Source of Truth β Trust the code and the tests; docs lag. (Docs rot, code compiles.)
- β "It's Not a Bug, It's a Feature" β Sometimes true. But used as a defense, it usually means the specification was never agreed. Ask which it is. (Adam Badow, Git.)
- β Rubber-Duck Debugging β Explain the problem out loud, line by line, to an inanimate object. Origin story: Brian Kemp's "Why the Hell Did I Use a Duck?" (1999), in which a colleague suggested a rubber duck because she had no programmer to talk to; the technique was then popularized by Ward Cunningham in his blikiwiki and spread through the agile community. The line from that story worth remembering: "If you explain to the duck why your program doesn't work, you'll see the error before you can even run it." The act of explaining forces you to externalize assumptions you were reading past.
- β
"The best debugger is a clear mind" β but pair it with real tools:
git bisect, a profiler, a test. Reasoning without measurement is guessing; measurement without reasoning is noise. You want both. - β Learn from Incidents, Not Just Features β Build; break it; learn; fix. The most valuable engineering happens after the failure, if you're paying attention.
- β Fight the Right Fight β Which problems are yours to solve, and which are noise? (Lindy? No.) Prioritize the ones that matter to users and the business; not every edge case is worth your time.
- β Keep a Decision Log β Not just what you did, but what you rejected and why. This is what makes the next engineer's job possible. (The most underrated artifact in most organizations.)
- β The Best Time to Refactor Was Before; the Second Best Is Now; the Third Best Is Never (because it's a rewrite)" β the general shape of the Boy Scout rule applied to timing.
- βοΈ "Read the Docs" β good advice; bad advice as a substitute for reading the code. Do both; the code is the contract.
- π¬ "The good news is, we don't need to improve anything." β Kent Beck, sarcastically, about test coverage as a goal.
- π¬ "It's not a test-driven-development problem if there's no tests." β Kent Beck, on a different topic, but a useful framing of when process advice doesn't apply.
- β Software Has Consequences β This is engineering, not neutral plumbing. Decisions about defaults, data collection, algorithmic recommendations, and access have real effects on real people.
- β Privacy Is Not a Feature, It's a Constraint β Collect the minimum, store it the shortest, encrypt it, be able to explain it. This is the basis of modern privacy regulation (GDPR, CCPA) and of good practice regardless of jurisdiction.
- β Do No Harm β To Users β Don't ship dark patterns: deceptive interfaces, confirmshaming, fake urgency, mislabeled buttons, and forced subscriptions. (Brignull / Mathur's taxonomy of dark patterns.) It's a design defect, not a cleverness feature.
- β Accessibility Is an Engineering Requirement, Not a Feature β WCAG, semantic HTML, keyboard navigability, screen-reader support, contrast. Why it's ethics and not just quality: it determines who can use your product at all. A web accessibility lawsuit is not a hypothetical.
- β Report Vulnerabilities Responsibly β When you find a security issue, follow a responsible-disclosure process (contact the vendor, give them time, publish with credit after a fix) rather than going public immediately. (Many programs now run bug bounties for this.)
- β Don't Build Surveillance You Don't Want to Exist β The "would you be comfortable if your feature were used against you or your family?" test. It's a real heuristic for whether a design is one you'd defend.
- β Be Honest About What Your Software Does β No dark patterns, no fake urgency, no misleading defaults. Technical honesty is a core engineering value. And never, ever deceive about security ("this is end-to-end encrypted" when it isn't).
- β Leave the Code Better Than You Found It β Including for the Next Person β Not just the code: the comments, the tests, the docs. Your successor is a real person, often a stranger, often you.
- β Know When to Say No β The most ethical engineering skill is declining to build the harmful thing well. It's a hard skill, and it's real work.
- π Algorithmic Fairness & Bias β Training data encodes past bias; models reproduce it at scale. Auditing for disparate impact is part of responsible deployment. (Cathy O'Neil, Weapons of Math Destruction.)
- π Security Researchers vs. Users β Responding responsibly to discovered vulnerabilities, running a bug bounty, and having a disclosure policy are signs of a mature organization.
- π¬ "You are morally responsible for what you put in the world." β The argument for engineering ethics (Van Peters, "The Most Important Feature"). The companion thought: most software is invisible; most damage is invisible; the people affected rarely see the code. That's precisely why it's your responsibility.
The best advice lives in a small number of primary sources. If you read these, you'll have covered most of the wisdom in this list with more nuance than any list can provide.
Design & Architecture
- Design Patterns: Elements of Reusable Object-Oriented Software β Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994). The GoF book. Start here.
- A Pattern Language β Christopher Alexander, Sara Ishikawa, Murray Silverstein (1977). The origin of the word "pattern" in software. Fascinating and different.
- Domain-Driven Design β Eric Evans (2003). Ubiquitous Language, bounded contexts, strategic design. Transformative if you're designing a system with real domain complexity.
- Patterns of Enterprise Application Architecture β Martin Fowler (2002). The pragmatic companion to DDD.
- Designing Data-Intensive Applications β Martin Kleppmann (2017). The single best book on systems design. Fallible only in being generous to the systems it covers.
- Building Microservices β Sam Newman (2nd ed., 2021). The balanced, un-hyped treatment.
- Software Architecture: The Hard Parts β Ford, Parsons, Komic (2020). Honest about distributed-systems tradeoffs.
- The Timeless Way of Building β Christopher Alexander. The philosophical root of pattern thinking.
- The Psychology of Computer Programming β Gerald Weinberg (1985). The human, still-relevant, uncomfortable half of engineering.
Language & Craft
- Code Complete β Steve McConnell (1991/2004). The definitive on code-level craft.
- The Pragmatic Programmer β Andy Hunt & Dave Thomas (1999). 20th anniversary edition (2019). The pragmatism in the title is a philosophy, not a buzzword.
- Clean Code β Robert C. Martin (2008). Genuinely good; also genuinely abused, often by people who missed the Boy Scout / "you won't always agree" framing. Read with judgment.
- The Clean Coder β Robert C. Martin. The professional-responsibility argument.
- Effective Java (3rd ed.) β Joshua Bloch. And Effective Go, Rust API Guidelines, Eloquent JavaScript for their own ecosystems.
- Structure and Interpretation of Computer Programs β Abelson & Sussman. The SICP of ideas; best for thinking about computation, not for a job.
- Programming Pearls / Programming in the Large β Jon Bentley / John Brooks. Column and essay collections with the occasional gem.
Product, Requirements & Release
- User Story Mapping β Jeff Patton (2005). The best practical book on slicing a product into releasable vertical slices; the antidote to building in layers.
- The Mom Test β Rob Fitzpatrick (2013). How to talk to users without leading them. Short, and the only requirements book here you'll finish.
- Inspired β Marty Cagan (2017). Product discovery, risk assessment, and why solutioning before you understand the problem is the expensive mistake. (Cagan's later Escaping the Build Trap is the sequel.)
- Shape Up β Ryan Singer (2018), free online. The most concrete treatment of scoping as a discipline: shaped work, hard timeboxes, and the "rabbit holes" list. Read this if your planning process feels perpetually late.
- Continuous Delivery β Jez Humble & David Farley (2010). The canonical book on making deployment routine and boring. The source of most Part VI.
- Accelerate β Forsgren, Humble, Kim (2018). The empirical research on what makes teams fast β and it is not most of what people guess.
- Semantic Versioning (semver.org) and the Keep a Changelog format. Small, free, and the two conventions worth standardizing on before you argue about them again.
Distributed Systems & Reliability
- The Site Reliability Book β Beyer, Jones, Petoff, Murphy (2016). Free online. The best practical resource for running systems.
- Designing Data-Intensive Applications (above) β the theory; this is the operations.
- Database Reliability β Goldman Sachs, 2020 (free online). A rare, honest account of what production databases really demand.
- Life Beyond Distributed Transactions β Werner Vogels, ACM Queue (2012). Free. The clearest short statement of why the industry abandoned two-phase commit.
- Kafka: The Definitive Guide β Confluent. The practical guide to one archetypal streaming system.
- The Tail at Scale β Dean & Barroso, CACM (2013). Free. The paper that made latency a first-class concern.
Testing & Quality
- Unit Testing: Principles, Practices, and Patterns β Vladislav Antonov / Steve Mesmans (2024). The modern, practical replacement for "xUnit Test Patterns."
- xUnit Test Patterns β Meszaros (2007). The taxonomy of test smells.
- Working Effectively with Legacy Code β Michael Feathers (2004). The seams technique and the practical art of getting under test.
- The Art of Unit Testing β Roy Osherov.
- Google Testing Blog / Software Engineering at Google β Chapter on testing; the "Test Sizes" framing is a great mental model.
Thinking & Wisdom
- The Mythical Man-Month & No Silver Bullet β Frederick Brooks. The essential/accidental complexity distinction and the 95% quote are both Brooks.
- Peopleware β Tom DeMarco & Timothy Lister. On why schedule pressure destroys teams, decades of evidence.
- The Soul of a New Machine β Tracy Kidder. On craftsmanship and flow.
- The Coming of Post-Industrial Capitalism / In the Age of the Smart Machine β Daniel Bell. Sometimes cited, over-interpreted; still a useful lens on knowledge work.
- The Foundations of C++ / Thinking Like a Computer Scientist β Allen Downey, freely available. Because the fundamentals matter and they're free.
- GΓΆdel, Escher, Bach β Douglas Hofstadter. The book that gives you the feel for what computation, mind, and meaning are. Skim or read cover to cover; either way it changes how you think.
Management & Team
- The Mythical Man-Month (above) β on adding people to a late project.
- Crucial Conversations β Stone, Heen, Patton. On the conversations that actually stall engineering work.
- The Goal β Goldratt. The origin of the Theory of Constraints; why work on the bottleneck.
- Turn the Ship Around! / Scaling Teams β Lars RendΓ³n, the Spotify model; a well-documented case study in caution.
- Accelerate β Forsgren, Humble, Kim. The empirical research on what actually makes teams fast β and it's not most of what people guess.
- The Staff Engineer's Path β Will Larson, and the Staff Engineer anthology (2023). The best current thinking on senior technical role, and on why you don't have to manage people to have impact.
- A Philosophy of Software Design β John Ousterhout (2nd ed., 2021). The most underrated design book of the 2010s. Deep modules, strategic vs. tactical programming, "design it twice," define errors out of existence. Free chapter 2 excerpt and errata at
web.stanford.edu/~ouster/aposd.php.
This list is opinionated, but it should not be sloppy. Two categories of sourcing problem show up in software-idiom lists, and both are worth naming:
1. Quotes that are widely misattributed. These are common enough that repeating them is a small credibility tax:
| Commonly repeated | Actual status |
|---|---|
| "If it's worth doing, it's worth doing badly" | Rob Pike said "if it's worth doing, it's worth testing." The "badly" version inverts the meaning. |
| "Premature optimization is the root of all evil" | Real (Knuth, 1974), but his full point includes that premature generalization is worse and that most premature optimizations are misguided. The truncated version is used to argue for sloppiness. |
| "Simplicity is the ultimate sophistication" | Leonardo da Vinci β but no primary text has been produced. Treat it as a modern aphorism wearing a Renaissance costume. |
| "Not everything should be made reusable" | Widely attributed to a pseudonymous author; the argument is sound, the citation is not. The defensible sources are Glass, Atwood, and Friedrichsen. |
| "Einstein: if you can't explain it simply, you don't understand it" | No evidence Einstein said this. He did say he'd fail a student who used jargon to show understanding rather than simply describing it β which is usually what the quote is trying to say. |
2. Quotes whose source is hard to pin down. These are often real ideas with unwieldy provenance β Zawinski's leaky abstractions, "software is a gas," "bikeshedding." Rather than fabricate a citation, this list marks them as attributed-but-unverified. If you can find a primary source, replace the hedge with the source. That is the single highest-value contribution anyone can make to this document.
3. Facts that go stale. Anything with a version number, a date, or a "current" status will rot. The Python/GIL entry above already carries explicit phases and dates so it can be checked rather than trusted. Prefer entries that state the tradeoff over entries that state a rule β rules rot, tradeoffs don't.
A note on the markers. β and βοΈ are claims about consensus, not about truth. A β entry is one where deviating requires a stated reason, not one that is guaranteed correct. And where an entry conflicts with another, the correct response is P59's question, not a search for the winner: the fallacy of the eternal big programmer (Woodside, Rothberg, Farley, 2019).
This list lives by being argued over. Contributions welcome, but with a bar:
Where it goes. Every entry belongs to a lifecycle stage (see the Lifecycle Map). If you can't name the stage, that's a signal worth thinking about β most weak advice is advice for no particular moment in the process. If a stage is thin, prefer filling a thin stage over adding a fifth opinion to a well-covered one; this document was substantially more useful after Part XII (retirement) than it was after another ten design principles.
A good addition has (at least):
- a clear one-line definition that a competent engineer would agree with;
- a "so what?" β the reason it matters, not just what it is;
- honest tradeoffs or an explicit marker that it's contested (βοΈ), and a note about when it doesn't apply;
- ideally a primary source, and a real-world example (good or bad);
- enough signal that a working engineer would find it useful in a design review.
Please don't add:
- framework- or library-specific "best practices" that go stale in six months (π exists for genuinely durable technique, not for a tool's current API);
- advice with no mechanism ("write better code," "care about quality") β if you can't say why it works, it's a slogan;
- something already covered β search the part it belongs to first (the Lifecycle Map says which);
- an entry with no obvious home β if you can't place it in the lifecycle, ask whether it earns a place at all.
- βοΈ **[Name]** β [one-line definition]. *So what:* [why it matters].
*Counter-argument / doesn't apply when:* [honesty].
(Source: [Author, *Work*, Year].)
Every entry here is a starting point, not a truth. Before adopting any of them, answer P59's question: this is a universal best practice, for whom, in what context, and toward what goal? If you can't answer that, you don't understand the practice well enough to apply it β which is itself the most useful lesson this whole list is trying to teach.