Key Principle
A module's value is measured by the ratio of functionality to interface complexity: "The benefit provided by a module is its functionality. The cost of a module (in terms of system complexity) is its interface" (Chapter 4). A deep module maximizes this ratio -- large functionality behind a small interface. A shallow module exposes an interface whose cost is not justified by its functionality.
The primary technique for creating depth is information hiding: each module encapsulates design decisions invisible to other modules (Parnas, 1972). This reduces complexity two ways -- simpler interfaces (less cognitive load) and easier evolution (changes affect one module only).
Critically, "hiding variables and methods in a class by declaring them private isn't the same thing as information hiding" (Chapter 5). Getters and setters on private fields expose just as much as public fields. True information hiding means the abstraction genuinely conceals a design decision that callers never need to know.
Abstraction can fail in two directions: (1) including unimportant details raises cognitive load, and (2) omitting important details creates a false abstraction -- "it might appear simple, but in reality it isn't" (Chapter 4).
Why This Matters
Without the depth lens, developers default to creating many small classes and methods, believing that "classes are good, so more classes are better" -- a disease Ousterhout calls classitis (Chapter 4). Each shallow module adds interface complexity without compensating functionality. The accumulated cost of many shallow modules exceeds the cost of fewer, deeper ones. "Small modules tend to be shallow" (Chapter 4) -- this directly contradicts conventional wisdom about class size.
Information leakage -- a design decision reflected in multiple modules -- forces coordinated changes and is the primary red flag for poor decomposition. The most dangerous form is back-door leakage: shared knowledge not visible in interfaces, which is "more pernicious... because it isn't obvious" (Chapter 5).
Good Examples
Unix file I/O: Five calls (open, read, write, lseek, close) hide hundreds of thousands of lines covering disk layout, permissions, caching, scheduling, and device management. The interface is tiny; the hidden functionality is enormous. (Chapter 4)
Garbage collectors: Zero interface, massive hidden functionality. The deepest possible module -- users benefit without even knowing it exists. "The best features are the ones you get without even knowing they exist" (Chapter 5).
Defaults as information hiding:
getParameter(String name)hides representation and provides type conversion;getParams()returningMap<String, String>leaks internal structure. Whenever possible, classes should "do the right thing" without being explicitly asked. (Chapter 5)
Counterpoints
Shallow module example:
addNullValueForAttribute(String attribute)wrappingdata.put(attribute, null). The interface is more complex than the implementation. Net effect: added complexity with no compensating benefit. (Chapter 4)Java I/O classitis: Reading serialized objects requires constructing FileInputStream, BufferedInputStream, and ObjectInputStream. Buffering should be a default, not a separate class. Three shallow classes where one deep class would suffice. (Chapter 4)
Temporal decomposition: Structuring code to mirror execution order distributes the same knowledge across stages. HTTP example: students split request handling into reading and parsing classes, but reading requires parsing (Content-Length determines body length), so both needed format knowledge. Fix: "Focus on the knowledge that's needed to perform each task, not the order in which tasks occur" (Chapter 5).
Back-door leakage: Shared knowledge not visible in interfaces creates unknown unknowns -- the worst form of complexity. Remedy: merge the leaking classes, or extract the shared knowledge behind a single abstract interface. (Chapter 5)
Key Quotes
"The benefit provided by a module is its functionality. The cost of a module (in terms of system complexity) is its interface." -- John Ousterhout, Chapter 4
"Hiding variables and methods in a class by declaring them private isn't the same thing as information hiding." -- John Ousterhout, Chapter 5
"The best features are the ones you get without even knowing they exist." -- John Ousterhout, Chapter 5
"Focus on the knowledge that's needed to perform each task, not the order in which tasks occur." -- John Ousterhout, Chapter 5
Rules of Thumb
- Measure a module by the ratio of hidden functionality to interface complexity, not by size or line count
- When two classes share knowledge of the same design decision, consider merging them or extracting shared knowledge behind a single interface
- Never structure modules around execution order; structure them around information boundaries
- Default to providing sensible defaults rather than forcing callers to configure everything
- Access modifiers (private/public) are not information hiding -- ask whether the abstraction truly conceals a design decision
- If a wrapper method's interface is as complex as its implementation, the wrapper adds net complexity
Related References
- Complexity: The Central Enemy - Depth realizes the encapsulation strategy against complexity
- Interface Design: Generality, Layers, and Complexity Placement - General-purpose interfaces and layer separation create deeper modules
- Strategic vs. Tactical Programming - Strategic mindset provides time to design deep modules