Steven's Knowledge

Simplicity

The four rules of simple design and the cost of complexity

Simplicity

Simplicity is harder than it looks. Most code is complicated not because the problem demanded it but because the author did not invest the effort to make it simple. Simple code reads as if the problem were easy; it earns that appearance through careful work.

This page brings together a small set of principles that, applied consistently, produce code worth maintaining.

Why Simplicity Matters

Software is read more than it is written, modified more than it is read, and maintained for longer than anyone planned. The ongoing cost of a system is dominated by the cost of changing it. Simple code is cheap to change; complicated code is not.

John Ousterhout's framing in A Philosophy of Software Design:

Complexity is incremental. You have to sweat the small stuff.

Each small piece of complexity — an unnecessary parameter, an unclear name, a misleading abstraction — seems too small to address. Each accumulates. The team eventually arrives at a codebase no individual decision created and no one wants to maintain.

The discipline is to push back at the small scale, every day.

Kent Beck's Four Rules of Simple Design

Beck's rules, popularized in Clean Code's Emergence chapter, give a priority order for evaluating a design. From most to least important:

1. Passes all the tests

Before anything else, the code must do what it claims. A "simple" design that does not pass its tests is not a design at all; it is a draft.

The implication beyond the obvious: if a piece of code is hard to test, it is hard to verify, which means its behavior is unknown. Untestable code is implicitly complex regardless of how clean it looks.

2. Reveals intention

The code makes its intent clear to a reader. Names match what they represent. Functions describe what they do. Structure mirrors the problem.

A reader should not need a guide to understand the code. The cost of failing this rule is paid by every reader, every time they read.

3. No duplication

Each piece of knowledge appears in exactly one place. The implications are covered in Duplication; the priority order matters here. Duplication is bad, but a misleading abstraction is worse — which is why this rule sits below intention-revelation.

4. Fewest elements

Among designs that satisfy the first three rules, prefer the one with the smallest number of classes, methods, and lines. Every additional element is something to read, understand, name, document, and maintain.

This rule is last for a reason: shrinking elements at the cost of clarity, correctness, or DRY makes the design worse. Apply it only after the other three are satisfied.

Applying the rules

Used in priority order, the rules give a working evaluation:

  • Does it work? (1) — if not, fix that first.
  • Is the intent clear? (2) — if not, rename, restructure, decompose.
  • Is anything duplicated? (3) — if so, consolidate.
  • Can anything be removed? (4) — if so, remove.

Most code reviews boil down to applying these rules. The vocabulary is shared even when the rules themselves are not invoked by name.

KISS — Keep It Simple

The acronym is older than software and still useful. The discipline behind it: prefer the boring solution to the clever one when both work. The boring solution can be modified by the next person; the clever one cannot.

A few habits that operationalize KISS:

  • Pick standard tools. A well-known library that fits the problem is better than a custom solution that fits it slightly better. The custom solution carries a maintenance tax forever.
  • Avoid unnecessary indirection. Each layer of abstraction is a layer the reader must navigate. Add layers when they earn their cost.
  • Prefer simple data over complicated logic. A configuration file that is harder to write but easier to read usually wins. Code that operates on flat data is usually clearer than code that maneuvers around nested objects.

Worse Is Better

Richard Gabriel's essay distinguishes two design philosophies:

  • The Right Thing. Get the design correct, complete, and consistent before shipping.
  • Worse Is Better. Get something simple working, ship it, learn from use, iterate.

Both have merit. The observation Gabriel made — and that has held up — is that simple-but-incomplete designs tend to win in practice, because they are easier to adopt, easier to understand, and easier to evolve. Complicated-but-correct designs lose because they are too expensive to adopt.

The lesson is not "be sloppy." It is that simplicity is a competitive advantage, not just an aesthetic preference. Code that is easy to understand attracts contributors and accumulates use; code that is difficult to understand stagnates.

The Cost of Complexity

Some complexity is essential — the problem is genuinely hard, and the code reflects that. Most is accidental — the code is hard because of the choices made by its authors.

Frederick Brooks, in No Silver Bullet, distinguished:

  • Essential complexity. Inherent in the problem.
  • Accidental complexity. Introduced by the solution.

The job of design is to eliminate accidental complexity. Common sources:

  • Premature abstraction. Generality built for needs that never materialized; see YAGNI.
  • Over-configuration. Knobs nobody adjusts, defaults nobody understands.
  • Unnecessary state. Variables that could be parameters, fields that could be locals, classes that could be functions.
  • Speculative flexibility. Hooks, plugin points, and indirection levels added "in case we need them."
  • Tooling sprawl. Each tool has a learning cost; the team that adopts ten when three would do pays the cost forever.

The prevention is constant editing. The same way a writer cuts text, a programmer cuts code.

When Complexity Is Justified

Not all complexity is bad. Sometimes the simple version is too slow, too risky, or too inflexible.

The decision matters:

  • Can you point to the problem the complexity solves? If not, it is speculative.
  • Have you measured the simpler alternative? A "necessary" optimization that has not been measured is often unnecessary.
  • Is the complexity localized? Complexity confined to one module is much cheaper than complexity that infects many.
  • Is the cost documented? Complex code that explains why it must be complex remains maintainable; complex code that does not, becomes mysterious.

Complexity introduced deliberately, justified concretely, and confined locally is part of engineering. Complexity introduced casually, without justification, anywhere it fits, is technical debt.

Practical Habits

A few habits that cumulatively produce simpler code:

Edit ruthlessly

After the code works, read it again with the goal of removing things. Most first drafts can be made shorter. The deletion pass usually improves clarity at the same time.

Resist abstraction until needed

Each new abstraction is a constraint on future change. Build the concrete thing. When the second concrete thing appears, see if they share an abstraction. By the third, the right shape is usually visible.

Choose narrow types and small interfaces

A function that takes the smallest type it needs (User instead of Request<User>) is composable. An interface with three methods is easier to implement than one with twenty.

Use the simplest data structure that works

A map is simpler than a class hierarchy. A list is simpler than a tree. A pure function over data is simpler than an object with state. Reach for complexity only when simplicity fails.

Read your code as a stranger would

After writing, before committing, read the diff as if seeing it for the first time. The mistakes that escape the author often catch the impartial reader on the same page.

Pre-Commit Checklist

  • Does the code pass the tests, including the ones that would fail if it were broken?
  • Can a reader unfamiliar with the change understand it without external explanation?
  • Is each piece of knowledge in one place?
  • Has every element you can remove without losing meaning been removed?
  • Is any complexity in the change justified by a concrete problem, with the cost documented?
  • Have you preferred the boring, standard solution over the clever one?

On this page