Gall's Law: Complex Systems That Work Evolved from Simple Systems That Worked

John Gall's observation is one of the most quietly reliable laws in engineering: a complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be made to work — you have to start over with a working simple system.

It sounds like folk wisdom. It is, in fact, a precise statement about how to change software safely — and it is the principle underneath every disciplined refactoring technique.

The 400-Line Method

Consider a method that places an order. It validates input, checks inventory, applies discounts, processes payment, sends email, writes the audit log, updates a reporting aggregate — 400 lines, all sharing variables, all executing sequentially. Eleven engineers have modified it over three years. Each one added a conditional, an early return, a flag. Nobody rewrote what was there; they added what was needed.

The method works. It passes all tests. But it is the most expensive code in the codebase, because adding a new payment method means reading 400 lines to know what will break. It resists change.

The instinct is to design a clean replacement from scratch. Gall's Law says that instinct is wrong. The clean replacement, designed in one move, will not work — it has not survived contact with the eleven edge cases the original absorbed. The way through is evolution: a sequence of small, individually-working states.

Refactoring Is Gall's Law as a Discipline

Refactoring is changing the internal structure of code without changing its observable behaviour. The test suite is the verifier — if all tests pass before and after, behaviour is preserved. Crucially, refactoring is not one big move. It is a sequence of behaviour-preserving steps, each of which leaves the system working.

Extract Method turns the 400-line monster into an orchestrator calling validateOrderInputs, checkInventoryAvailability, applyDiscounts, processPayment — each independently testable. But the value is not just the end state. It is that every intermediate state still works. You never leave the system broken between commits. That is a working simple system evolving, step by step, into a working complex one.

The most practical form is preparatory refactoring: refactor to make a change easy, then make the easy change. Before adding Klarna as a payment provider, extract a PaymentGateway interface and move Stripe behind it. Now implementing Klarna is a new class, not a 400-line modification. And the refactoring and the feature go in separate pull requests — the refactoring is safe because behaviour is preserved; the feature is clean because it lands in well-structured code.

Two paths from a working simple system to a working complex one

  evolution (Gall's Law)         big-bang rewrite
  ─────────────────────          ────────────────
  S0  ✓                          S0  ✓
   │ extract method                  │
  S1  ✓  every state works           ╳  200 files at once
   │ extract class                (no working
  S2  ✓  tests pass each step      intermediate state)
   │ preparatory refactor             │
  S3  ✓  ◀── ship from here       S?  ╳ ── nowhere to fall back

The Tradeoff: Simplicity vs Flexibility

A codebase before refactoring is locally simpler — fewer classes, fewer files — but globally inflexible, because every change requires understanding the full context. After refactoring it is locally more complex — more abstractions, more indirection — but globally flexible, because each piece changes independently. That is AT3 (Simplicity vs Flexibility).

The tradeoff tips toward refactoring when the cost of change in the current structure exceeds the cost of the refactoring effort. The signal is concrete: when adding any new feature requires reading more than 200 lines of existing code to know what might break, Extract Method and Extract Class are overdue.

Where It Fails: The Big-Bang Rewrite

Gall's Law has a dark twin — the failure that happens when you ignore it. A large-scale refactor that touches 200 files in one pull request creates a single point where one misunderstanding cascades through every changed file. That is FM2 (Cascading Failures): the big-bang change has no working intermediate state to fall back to, so a flaw anywhere contaminates everywhere.

The defence is exactly what Gall prescribes: one refactoring step at a time, verified by tests, merged separately. The strangler fig — wrap the old code in an abstraction, build the new implementation behind it, switch callers incrementally, delete the old code when no callers remain — is Gall's Law made into a migration strategy. Google did it at browser-engine scale, forking WebKit into Blink: the rendering interface stayed stable while the implementation was reorganised underneath it.

There is a second failure. Refactoring without tests is not refactoring — it is rewriting with unknown consequences. A moved method that now operates on a different object's state looks identical and produces wrong results for specific inputs, silently — FM9 (Silent Data Corruption). The "behaviour-preserving" guarantee is unverifiable without a test suite. For legacy code with no tests, characterisation tests (record the current outputs, make them the expected values) build the safety net before the first extraction.

The One Sentence

Gall's Law says you cannot design a working complex system from scratch — you can only evolve one from a working simple system — and that is exactly why refactoring is a sequence of small, test-verified, behaviour-preserving steps: every intermediate state must work, because the big-bang rewrite that skips them has no working state to return to.

Concept: Gall's Law — a working complex system is always found to have evolved from a working simple system; one designed complex from scratch cannot be made to work.

Core Idea: Refactoring operationalises Gall's Law — a sequence of small, test-verified, behaviour-preserving steps, each leaving the system working.

Tradeoff: AT3 — Simplicity vs Flexibility: pre-refactor code is locally simple but globally rigid; post-refactor code is locally complex but globally flexible.

Failure Mode: FM2 — Cascading Failures: a big-bang refactor across 200 files has no working intermediate state, so one misunderstanding contaminates everywhere.

Signal: When adding a feature requires reading more than 200 lines to know what might break, Extract Method is overdue.

Series: Book 5, Ch 16