Library
A Philosophy of Software Design · 2 of 11
A Philosophy of Software Design
AI Software Development CRITICAL

Complexity: The Central Enemy

complexity dependencies obscurity change-amplification cognitive-load unknown-unknowns

Key Principle

Complexity is "anything related to the structure of a software system that makes it hard to understand and modify the system" (Chapter 2). It is the root constraint of all software development: "The greatest limitation in writing software is our ability to understand the systems we are creating" (Chapter 1).

Overall system complexity is weighted -- each part's complexity multiplied by how often developers touch it. Isolating complexity in rarely-touched code is almost as good as eliminating it. Complexity is reader-defined, not writer-defined: "If you write a piece of code and it seems simple to you, but other people think it is complex, then it is complex" (Chapter 2).

Three symptoms reveal complexity, ranked from least to most dangerous:

  1. Change amplification -- a simple change requires modifications in many places. Visible, so least dangerous.
  2. Cognitive load -- how much a developer must know. Lines of code is a poor proxy.
  3. Unknown unknowns -- you don't know what you don't know. The worst symptom because you cannot compensate.

Two root causes generate all three symptoms:

  • Dependencies -- unavoidable but manageable; good design replaces nonobvious dependencies with obvious, searchable ones.
  • Obscurity -- information that isn't obvious; generic names, undocumented units, inconsistency.

Two strategies fight complexity, and they are complementary:

  1. Elimination -- make code simpler and more obvious. Remove special cases, use consistent naming.
  2. Encapsulation -- divide the system so a programmer can work on one module without understanding others.

Why This Matters

Without treating complexity as the primary design variable, teams treat it as an unavoidable cost of features. They optimize for shipping speed and accumulate cognitive debt that compounds silently until the system becomes unmodifiable.

Complexity accumulates through hundreds of small decisions, not single catastrophic errors. Each addition seems harmless -- without zero-tolerance, no individual commit seems worth blocking, yet the aggregate destroys velocity. Software design is never done: "Incremental development means that software design is never done" (Chapter 1). Teams that treat design as a completed phase patch around discovered problems without revising overall structure, producing "an explosion of complexity" (Chapter 1).

Good Examples

  • Weighted complexity: A hairy algorithm buried in a stable utility module costs less than a mildly confusing pattern in a frequently-edited controller. Prioritize simplification by exposure frequency. (Chapter 2)

  • Cognitive load vs. lines of code: "Sometimes an approach that requires more lines of code is actually simpler, because it reduces cognitive load" (Chapter 2). A C function that allocates memory and forces callers to free it adds cognitive load; restructuring so the allocator also frees eliminates it.

  • Red flags as shared vocabulary: Concrete signs that code is more complicated than necessary enable productive code review -- shifting conversations from subjective taste ("I don't like this") to diagnosable pattern ("this is a shallow module" or "this is information leakage"). (Chapter 1)

Counterpoints

  • Banner-color example: A central variable fixes the obvious dependency, but a derived emphasis color creates a hidden one -- changing the banner breaks styling nobody thought to check. This is an unknown unknown, the worst symptom, because you cannot compensate for what you do not know to look for. (Chapter 2)

  • Change amplification is visible but deceptive: It is the least dangerous symptom because developers notice it, but teams often treat it as the primary problem while ignoring the hidden unknown unknowns lurking in the same design. (Chapter 2)

  • Documentation as red flag: "The need for extensive documentation is often a red flag that the design isn't quite right" (Chapter 2). Teams paper over obscurity with docs instead of fixing the underlying structural problem.

Key Quotes

"The greatest limitation in writing software is our ability to understand the systems we are creating." -- John Ousterhout, Chapter 1

"If you write a piece of code and it seems simple to you, but other people think it is complex, then it is complex." -- John Ousterhout, Chapter 2

"The most fundamental problem in computer science is problem decomposition: how to take a complex problem and divide it up into pieces that can be solved independently." -- John Ousterhout, Preface

"Incremental development means that software design is never done." -- John Ousterhout, Chapter 1

Rules of Thumb

  • Treat complexity as a design variable you control, not an unavoidable cost of features
  • Weight complexity by exposure: ruthlessly simplify high-traffic code, tolerate complexity in rarely-touched modules
  • When you find yourself writing extensive documentation, suspect a design problem
  • Unknown unknowns are the worst symptom -- prefer designs that make dependencies obvious and searchable
  • Every principle has limits; "beautiful designs reflect a balance between competing ideas and approaches" (Chapter 1)
  • Software design is a learnable craft, not innate talent -- quality comes from deliberate practice and feedback

Related References