Skip to content

Design by Principles

Chapter Info

Calculating... Writing Progress: 80%

!!Software design is the discipline of making deliberate choices before complexity makes them for us. It is not about rigid rules or fashionable patterns, but about understanding the forces that shape a system — change, coupling, scale, human constraints, and time — and choosing structures that keep the system understandable as it grows.!!

alt text

Introduction [Introduction] {1}

The Big Ball of Mud: The Default [Big Ball of Mud] {1}

!!The most common architecture in the world is no architecture.!! Foote and Yoder gave it a name — the Big Ball of Mud — precisely because it is the default outcome of building software without deliberate design: code accretes without boundaries, every part touches every other part, and after a few years no one remembers why anything is where it is. It is not the result of bad intentions; it is the result of no decisions. Every principle in this chapter exists for one reason — to push back against this silent gravity.

alt text

Brian Foote and Joseph Yoder

Big Ball of Mud (1997).

What is Software Design? [What is Design] {1}

!!Software design is the art of solving real-world problems through systems that must evolve over time.!! Requirements shift, technologies evolve, user needs grow — the challenge is not just building something that works today, but creating systems that can adapt tomorrow without collapsing under their own complexity. The enemy is complexity accumulation. Every feature added, every shortcut taken, every "temporary fix" that becomes permanent adds weight to the system. What once took hours now takes weeks. What was once clear becomes opaque. What was once flexible becomes rigid.

alt text

Robert C. Martin

Clean Architecture: A Craftsman's Guide to Software Structure and Design (2017).

Good Design Is Built One Decision at a Time [One Decision at a Time] {1}

Good architecture is not the result of one brilliant decision. It is shaped by countless choices, large and small, that reinforce or undermine one another. A shortcut may seem harmless in isolation, but repeated shortcuts create complexity; deliberate choices can instead establish clear boundaries and a coherent structure. No single principle guarantees a good architecture. Together, principles provide a shared direction for making decisions that strengthen the system rather than gradually weaken it.

alt text

Design Is Everywhere — And Everyone's Business [Design is Everywhere] {1}

Design appears in every decision across the software lifecycle: naming a variable, structuring a function, defining a deployment path, shaping logs, choosing infrastructure, or defining the architecture of a system. Design is everyone’s responsibility. Software engineers, DevOps, security, infrastructure, platform teams, and managers all shape the system through their choices, and every one of those choices leaves a mark on readability, maintainability, reliability, and long-term adaptability.

alt text

Design is a Journey [Design as a Journey] {1}

!!Design is not a one-time act — it is a continuous, iterative process shaped by dialogue, feedback, and confrontation with reality.!! Unlike a destination you can reach and declare complete, design is an ongoing journey that never truly ends. Each decision opens new paths, each solution reveals new challenges, and each implementation teaches new lessons. Good design evolves through trial and error, conversations with stakeholders, and the tension between conflicting needs. The first version is rarely the best — it's simply the starting point.

alt text

Design Mindset [Design Mindset] {1}

Vision Sets the Direction [Vision Sets Direction] {1}

Vision is what gives a system a direction. !!It is the long horizon a system is moving toward — and direction is the shape of every step taken in its name!!. With a vision, isolated decisions align: every change becomes a small bet on a future the team has agreed to build, and over time those bets compound into architecture. Without it, the same decisions are only locally reasonable — globally incoherent — and the system erodes by accumulation, one well-meaning increment at a time. Vision is what turns isolated decisions into a coherent system. It is the difference between motion and direction.

alt text

Design Without Borders [Design Without Borders] {1}

Most design happens under a narrow lens: a single ticket, a single sprint, a single team. The change is defined to fit inside the territory that team owns — and that is exactly the trap of micro-design. Our service, our code, our infrastructure become artificial boundaries for the solution. But the system does not respect those boundaries: dependencies, data, failures, latency, security risks, and complexity cross them freely. !!Good design steps outside the boundaries of ownership and asks what is right for the system as a whole.!! alt text

Have the Courage to Design Without Borders [Design Courage] {1}

Design without borders requires the courage to leave the territory we control, enter territories owned by others, and challenge decisions or priorities along the way. It means convincing, negotiating, and sometimes facing conflict to pursue what is right for the system as a whole.

alt text

Design with Your Feet on the Ground [Reality Bridge] {1}

!!Good design starts with understanding the reality in which the system must live.!! Before thinking about patterns, technologies, or architectural ideals, understand the business, the customers, the budget, the people, the existing stack, the legacy, and the constraints that shaped it. Theory provides principles and ideas, but architecture does not happen in theory. A good architect keeps their feet firmly on the ground and uses principles to improve the world that actually exists — not to design a perfect system for an imaginary organization.

alt text

Model the Business [Model the Business] {1}

A system should be built around the concepts of the business it serves — customers, orders, transfers, accounts — not around technical abstractions invented for the developer’s convenience. Recording a financial transfer and recording a user like may look identical at the code level — both write a row, both return a status — yet the business meaning is completely different: one demands auditability, strong consistency, and strict authorization; the other can tolerate much simpler guarantees. When code and business share the same vocabulary, the system becomes easier to discuss, easier to evolve, and far less likely to drift away from what it is supposed to do. This is the core idea behind Domain-Driven Design (DDD): the architecture is shaped by the domain, not by the database schema or the framework.

alt text

Keep Complexity Under Control [Simplicity First] {1}

Easy Is Not Always Right [Choose Wisely] {1}

The easiest choice today is not always the right choice. Ease is a powerful bias: we naturally prefer the solution that fits what we already know, what we already own, and what requires the least resistance. But good design is not about minimizing today’s effort. It is about making the choice that best serves the system over time, even when that choice is harder today.

alt text

Rich Hickey

Simple Made Easy (2011) distinguishes simplicity from ease.

The Only Way to Go Fast Is to Go Well [Go Fast Well] {1}

In his seminal book Clean Architecture: A Craftsman's Guide to Software Structure and Design, Robert C. Martin states that "The only way to go fast is to go well" is one of the core principles of sustainable software development. !!In the long run, speed and quality are not opposing forces.!! Clean, well-structured code is what allows teams to keep moving quickly over time. Poor design may create the illusion of speed at the beginning, but it eventually turns every change into a negotiation with technical debt.

alt text

The Quest for Simplicity: Foundation of All Principles [Quest for Simplicity] {1}

Software design is guided by many principles — modularity, testability, security, scalability, resilience. Each one matters. But !!one principle sits above all others: simplicity!!. Without simplicity, every other principle becomes harder to achieve. A complex system resists testing. A complex architecture hides security flaws. Complex code slows scalability. Simplicity is not just one principle among many — it is the foundation that makes all others possible. It is what allows a system to remain understandable, changeable, and trustworthy over time.

alt text

Learn More

Chapter: Design for Simplicity.

YAGNI – Avoid Over-Engineering [YAGNI Principle] {1}

"You Aren't Gonna Need It" (YAGNI) reminds us not to build features or systems before they are necessary. !!Over-engineering adds unnecessary maintenance and complexity without guaranteed value.!! Build for what's needed today, and design with enough flexibility to adapt tomorrow.

alt text

Ron Jeffries and Extreme Programming

YAGNI ("You Aren't Gonna Need It") is an Extreme Programming practice associated with Ron Jeffries.

Walk Before You Run [Walk Before You Run] {1}

!!Earn each level of sophistication before climbing to the next!!. Before designing for multi-cloud, master a single cloud. Before optimizing for millions of users, make it work for thousands. Before building abstractions that span multiple contexts, prove value in one. The principle is not about lack of ambition — it is about earning your complexity: each level should be justified by an actual constraint you are facing, not by a hypothetical future. Teams that rush to advanced patterns without mastering the fundamentals accumulate complexity they have no experience managing.

alt text

Stop Adding Your Own Complexity [Complexity Types] {1}

Some complexity belongs to the problem and cannot be avoided. But systems often become more complex than the problems they solve because we add complexity of our own: unnecessary abstractions, confusing structures, too many layers, or over-engineered solutions. Good design accepts the complexity the problem requires and eliminates the complexity we create ourselves.

alt text

Frederick P. Brooks, Jr.

No Silver Bullet—Essence and Accidents of Software Engineering (1986) distinguishes essential complexity inherent in a problem from accidental complexity introduced by its implementation.

Don't Optimize What Shouldn't Exist [Eliminate Before Optimize] {1}

!!The fastest, cleanest code is the code you never need to write — or keep.!! Before tuning queries, parallelizing pipelines, or shaving milliseconds, first ask whether the feature, workflow, or even the system is necessary at all. Optimization entrenches waste by making it feel valuable and harder to remove later. Good design deletes, simplifies, and merges until only the essential remains; then, and only then, optimize what demonstrably matters. Every optimization carries maintenance cost, cognitive load, and lock‑in. Eliminate and clarify first; optimize only after necessity is proven by real usage and measurable value.

alt text

Make the System Smart So Consumers Don't Have to Be [System Smart] {1}

A well-designed system absorbs complexity so its consumers don’t have to. Whether serving users, developers, or other services, it should provide sensible defaults, automatic recovery, and clear errors. This may increase internal complexity, but it prevents every consumer from solving the same problems. Put complexity where it can be managed once, not repeated everywhere.

alt text

Design with Purpose [Design with Purpose] {1}

Focus Design Where It Matters [Strategic Design Investment] {1}

Not all parts of a system deserve the same design investment. Stable areas should remain simple. Volatile areas — business rules, integrations, scaling points, user workflows — deserve stronger boundaries, abstractions, and tests. Good design is not about making everything flexible. It is about identifying where change is likely and protecting those areas from becoming expensive to modify. Over-designing stable parts creates unnecessary complexity; under-designing volatile parts creates future pain. !!Design effort should follow expected change.!!

alt text

Design Starts at Time Zero [Design Time Zero] {1}

The fundamental properties of a system are not features you add later — they are designed in from the start. Security, availability, scalability, observability, testability, performance, and resilience are shaped by early architectural decisions. Design early for what is expensive to retrofit; grow capacity and sophistication as real needs emerge. Time zero is the moment of design: how components communicate, where state lives, and how failures are handled shape what the system can become. Treating these qualities as afterthoughts is one of the most common and costly mistakes in software development.

alt text

A Deliberate Tension

This principle can seem to conflict with YAGNI and Walk Before You Run — and that tension is real. Both are true. Design early for decisions that are expensive to reverse, but delay capacity, sophistication, and abstractions until real needs justify them. Knowing what must be decided now and what should remain open is part of the difficulty of good design.

Solve Problems at the Source, Not the Symptoms [Fix the Real Problem] {1}

!!Mature design distinguishes symptoms from causes.!! Addressing only what is visible or urgent produces superficial fixes that bring momentary relief while the underlying issue keeps growing. Each workaround adds friction and hides the real problem deeper. True simplicity comes from intervening where the cause lives, not where the pain appears — repeatedly asking why, challenging assumptions, resisting the comfort of the quick fix.

alt text

"We Can Clean It Up Later" Is Not a Strategy [Cleanup Fallacy] {1}

"We can clean it up later" rarely happens. Market pressure does not subside, the urgency to ship only grows, and technical debt compounds faster than any team's promise to itself. Each shortcut makes the next change harder, until the system is so expensive to modify that "later" is permanently delayed. "We’ll clean it up later" is not a design strategy. Build clean enough today, and improve the design continuously as the system evolves. Deliberate technical debt can be a valid trade-off when it is explicit, bounded, and accompanied by a clear reason and a plan to revisit it.

alt text

Continuous Refactoring Shapes Sustainable Systems [Continuous Refactoring] {1}

!!Refactoring is not an occasional cleanup task; it is how design quality survives.!! A healthy codebase evolves through small, continuous improvements that keep complexity manageable and prevent technical debt from hardening. Waiting for a future refactoring sprint rarely works. Refactoring should be part of normal development: when a feature is done, the code should not only work, but remain understandable, testable, and safe to change.

alt text

Related chapters

Design for Simplicity and Design for Testability cover the structural qualities that continuous refactoring protects.

Making Decisions

Design is About Making the Right Decision [Right Decision] {1}

There is no perfect design in absolute terms. There are only decisions that fit a specific context and set of priorities. !!Every design is a trade-off: performance vs. simplicity, flexibility vs. speed, scalability vs. cost.!! Good design communication makes these trade-offs visible.

alt text

Principles Allow Exceptions — But Make Them Deliberate [Conscious Deviation] {1}

Design principles are not laws. Context can justify an exception, but exceptions must be made deliberately. Before deviating, understand what the principle protects, why your context is different, and what trade-off you are accepting. Every exception also creates a precedent: others may repeat the decision without understanding why it was justified in the first place. An exception should therefore be rare, explicit, and documented — not only for today’s decision, but for the decisions it may encourage tomorrow.

alt text

Plan First [Plan First] {1}

A lightweight plan before implementation helps separate deliberate engineering from improvisation. Long before AI, planning first — sketching the direction, identifying constraints, and breaking the problem into steps — was what kept teams from building the wrong thing fast. With AI, the principle becomes even more important: once generation starts in a particular direction, what has already been produced influences what comes next. A weak initial direction can therefore shape an entire solution before its assumptions have been challenged. Whether the typing is done by a human or a machine, thinking before building is what makes the result worth keeping.

alt text

Know When to Stop Designing [When to Stop Designing] {1}

Design is iterative, but iteration needs a stopping point. Keep refining while each pass resolves meaningful uncertainty, reduces risk, or makes the system materially better. Stop when further improvement becomes mostly speculative or adds more complexity than value. Good design is not perfect design — it is good enough for what you know today, and easy enough to revisit tomorrow.

alt text

Use Precise Language for Requirements [Requirement Language] {1}

Design discussions become ambiguous when required, recommended, optional, and nice to have are used interchangeably. RFC 2119 provides a useful vocabulary: MUST means the requirement is mandatory; SHOULD means there may be valid reasons to deviate, but the implications must be understood; MAY means it is truly optional. A nice to have is weaker still: desirable, but not a requirement. Precise language makes constraints explicit and prevents preferences from quietly becoming obligations — or obligations from being mistaken for suggestions.

alt text

Living with Decisions

Document the Why, Not Just the What [Document the Why] {1}

!!Code shows what the system does. Documentation shows why!!. Without the why, future teams are forced to guess from the code alone — and they will guess wrong, because code rarely encodes the constraints, assumptions, and trade-offs that shaped it. Good documentation is not bureaucracy, it is memory. An ADR only needs to capture the context, the decision, the alternatives rejected, and the consequences accepted.

alt text

Michael Nygard

Documenting Architecture Decisions (2011) introduced the Architecture Decision Record (ADR) format widely used today.

Related chapter

Design by Process covers the practices that make architectural decisions and delivery work repeatable.

Revisit Decisions When Reality Changes [Revisit Decisions] {1}

A design decision that was right on day one may not be right on day five hundred. !!Decisions are not permanent truths — they are responses to a context that itself evolves!!: requirements, scale, team maturity, technology, business priorities. A monolith may be the right choice when a product is young and speed matters most; the same monolith may become a bottleneck when the organization grows and teams multiply. The original decision was not wrong — reality simply moved. Mature teams revisit their architecture deliberately, through small and safe changes, instead of waiting for the moment when a painful rewrite becomes the only option.

alt text

The Ripple Effect [Ripple Effect] {1}

!!At the system level, a single design decision cascades like a ripple on a pond.!! A seemingly minor choice in code structure today can reshape performance tomorrow; a quick fix in one service can quietly compromise scalability across the whole system; a slightly inefficient query may only reveal its impact under load; a hastily designed API may limit future integrations for years. Every decision — from variable naming to schema design, from a multi-tenancy model to a database choice — carries weight beyond its immediate context, because software systems are interconnected ecosystems where microscopic choices trigger macroscopic effects.

alt text

Every Decision Writes Company DNA [Decisions Become DNA] {1}

The ripple effect doesn't stop at the system — it shapes the organization itself. !!No single decision writes a company's DNA, but their accumulation does.!! When inconsistencies, shortcuts, and badly-made choices pile up across years and teams, they stop being individual mistakes and become the organization's identity: the way the company builds, the things that become increasingly difficult to change, the constraints every future team inherits without ever choosing them. A company's DNA is not declared in a strategy document — it is written, one quiet decision at a time, by the cumulative weight of everything that was ever shipped.

alt text

AI and Design [AI and Design] {1}

AI Has Ideas — You Have the Context [AI Needs Context] {1}

!!AI can know a great deal about software design, but it does not naturally live inside the reality of your organization.!! It can propose architectures, patterns, and alternatives, but good design depends on context that is often implicit, fragmented, and constantly changing: conversations with customers, feedback from developers, budget pressure, team skills, organizational tensions, legacy systems, and the reasons behind today's stack. AI can become a powerful design partner when given that context, but the architect remains responsible for connecting ideas to reality. The harder that reality is to capture, the more important human judgment becomes.

alt text

Plan Before You Generate [Plan Before Generate] {1}

AI makes it tempting to jump directly from an idea to implementation. A prompt becomes code, infrastructure, APIs, and sometimes an entire architecture before the important decisions have even been discussed. !!Use AI to plan before you use it to build.!! Ask it to clarify the problem, identify constraints, expose assumptions, propose alternatives, and compare trade-offs first. Architecture should emerge from deliberate decisions — not from the first plausible solution the model happens to generate.

alt text

Make AI Explore Before It Converges [Explore Before Converging] {1}

AI is very good at producing a plausible solution quickly — sometimes too quickly. The first architecture it proposes can anchor the rest of the discussion, even when better alternatives exist. Before choosing, ask for genuinely different designs and make the trade-offs explicit: simplicity, cost, performance, reliability, operability, and future change. !!Use AI to widen the design space before asking it to narrow it.!!

alt text

AI Should Challenge the Design, Not Just Complete It [AI as Design Critic] {1}

AI is most useful in design when it does more than agree with the direction already chosen. Ask it to find hidden assumptions, failure modes, unnecessary complexity, missing constraints, and reasons the proposed architecture might not survive change. A model that only completes your thinking can reinforce your blind spots. A model asked to challenge it can expose them. !!Use AI as a design critic, not only as a design generator.!!

alt text

Preserve the Context, Not Just the Code [Preserve the Prompt] {1}

When AI generates code, the prompt can preserve valuable context that the code itself cannot: the intent, constraints, assumptions, and instructions that shaped the result. But a prompt is not a design record — it may not capture the alternatives considered, the reasoning behind a decision, or the trade-offs accepted. Preserve useful prompts as part of the design context, alongside ADRs and other documentation — not as a replacement for them.

alt text

Next chapter

Design by Structural Principles turns these principles into concrete structure: Separation of Concerns, Single Responsibility, Open-Closed, and Dependency Inversion.