Chapter 2: The Nature of Complexity
Core Idea
Section titled “Core Idea”Complexity is anything about a system’s structure that makes it hard to understand and modify. It shows up as change amplification, cognitive load, and unknown unknowns. It is caused by dependencies and obscurity, and it builds up in many small increments.
Frameworks Introduced
Section titled “Frameworks Introduced”- The practical definition of complexity: “Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.”
- It is about the developer’s experience at a point in time with a particular goal, not about size or feature count. A large, sophisticated system that is easy to work on is not complex. A small one can be very complex.
- Cost-benefit view: in a complex system, small improvements take a lot of work. In a simple system, larger improvements take less effort.
- The complexity formula: C = Σ_p c_p · t_p. The overall complexity of a system (C) is the complexity of each part p (c_p) weighted by the fraction of time developers spend working on that part (t_p).
- When to use: deciding where complexity matters. A very complicated part that almost nobody touches adds little.
- Consequence: “Isolating complexity in a place where it will never be seen is almost as good as eliminating the complexity entirely.”
- The three symptoms of complexity:
- Change amplification: a seemingly simple change requires modifications in many places.
- Cognitive load: how much a developer needs to know to complete a task. It comes from APIs with many methods, global variables, inconsistencies, and dependencies between modules.
- Unknown unknowns: it is not obvious which code must change, or what information you need. This is the worst symptom, because you can’t find out what you’re missing until bugs appear.
- The two causes of complexity:
- Dependencies: a piece of code “cannot be understood and modified in isolation.” Dependencies lead to change amplification and cognitive load.
- Obscurity: “important information is not obvious.” Obscurity creates unknown unknowns and adds to cognitive load.
- How to use: you can’t remove all dependencies (every class creates some around its API), so reduce their number and make the remaining ones simple and obvious.
Key Concepts
Section titled “Key Concepts”- Obvious system: one where a developer can make a quick guess about what to do, without thinking very hard, and be confident the guess is correct. It is the opposite of cognitive load and unknown unknowns (ch18).
- Dependency: a relationship that forces you to consider or change other code when you change this code. Examples: a protocol’s sender and receiver; a method’s signature and its callers.
- Obscurity: missing or hidden information. Examples: a variable called
timewith no units; a message table you must update when you add an error status, with nothing pointing to it; one name used for two purposes. - Complexity is incremental: it comes from “hundreds or thousands of small dependencies and obscurities,” not from one big mistake.
- “Zero tolerance” philosophy: the response to incremental complexity, developed in ch03.
Mental Models
Section titled “Mental Models”- Judge complexity from the reader’s side. “Complexity is more apparent to readers than writers.” If others find your code complex, it is complex, and the gap between your view and theirs is worth investigating.
- Don’t measure complexity in lines of code. A framework that lets you write an app in a few lines can still be very hard to use. More lines can be simpler if they lower cognitive load.
- Think of a good design as trading a nonobvious dependency for an obvious one. The compiler and a name search can then manage it for you.
- Treat a need for extensive documentation as a design signal. “The need for extensive documentation is often a red flag that the design isn’t quite right.”
- Recognizing complexity is easier than creating simplicity. Use that ability: if a design looks complicated, try another approach.
Anti-patterns
Section titled “Anti-patterns”- Excusing one small increment of complexity: it is easy to tell yourself one change won’t matter. If every developer does this on every change, complexity grows fast and is hard to remove later.
- Hidden coupling: values copied into many places, or derived values that must stay in sync with a shared one without any visible link.
- Making the caller clean up: a C function that allocates memory and expects the caller to free it adds cognitive load and invites leaks. Let the module that allocates also free.
Worked Example
Section titled “Worked Example”The colored banner on a Web site (Figure 2.1).
- (a) Change amplification. Every page sets its own banner color:
bg = "red"on each page. Changing the site’s color means editing every page by hand, which is nearly impossible with thousands of pages. The background color creates a dependency between all the pages. - (b) The fix. Every page uses
bg = bannerBg, and a single shared variablebannerBgholds"red". One change updates the whole site. The dependency between pages is gone, replaced by a dependency on thebannerBgAPI. That new dependency is better because it is obvious: you can search for the name, and the compiler flags any page that uses an old name. - (c) Unknown unknowns. Some pages also set
emph = "darkred", a darker shade of the banner color used for emphasis, written directly in those pages. If someone changesbannerBg, the emphasis color no longer matches. Nothing in the code tells the developer that the emphasis color depends on the banner color, or which pages use it. The only way to find out is to search every page. - Lesson. The design in (b) reduces change amplification. The design in (c) shows that a system can look easy to change and still hide an unknown unknown. A better design would derive the emphasis color from
bannerBgin the same central place.
Key Takeaways
Section titled “Key Takeaways”- Define complexity by how hard the system is to understand and modify, not by its size.
- Watch for the three symptoms: many edits per change, too much to know, and not knowing what to know.
- Trace each symptom to its causes, dependencies and obscurity, and fix those.
- Spend your simplification effort where developers spend their time (the C = Σ c_p t_p formula).
- Make the dependencies you keep obvious and searchable.
- Don’t accept small increments of complexity, because they are how systems become complex.
Connects To
Section titled “Connects To”- Ch 3: the zero-tolerance, strategic mindset that stops incremental complexity.
- Ch 4–5: deep modules and information hiding reduce dependencies.
- Ch 13–14: comments and names reduce obscurity.
- Ch 18: techniques for making code obvious.