Key Principle
Deciding what belongs together versus apart should be driven by information hiding and depth, not by code length or organizational tidiness. When pieces share information, are always used together, or are hard to understand without each other, combine them. When errors arise, reduce them structurally: redefine semantics so the "error" is normal behavior, mask exceptions at low levels, aggregate many exception types into one handler, or crash outright. For every major design decision, sketch at least two radically different approaches before committing.
Why This Matters
Splitting code is treated as self-evidently good, but it has concrete costs: more components to track, additional management code, hidden dependencies across pieces, and duplication. Developers who split by length rather than by abstraction produce shallow methods that callers must invoke in sequence -- the split destroyed locality without creating real abstraction. Similarly, exception handling is a leading source of complexity because handlers are hard to write, rarely tested, and rarely executed. A study of distributed systems found that "more than 90% of catastrophic failures ... were caused by incorrect error handling" (Chapter 10). Every exception a class can throw is part of its interface; more exceptions mean a shallower class.
Good Examples
Combining HTTP request reading and parsing into one class eliminated duplicated format knowledge and made the code shorter and simpler. Separate methods forced both reader and parser to understand Content-Length and request boundaries. (Chapter 9)
Unix file deletion defines the "file in use" error out of existence: the name is removed immediately, and data is freed only after all handles close. Both the "file in use" error and the "disrupted reader" error vanish. (Chapter 10)
TCP masks packet loss by resending internally -- higher-level code never sees the exception. NFS retries indefinitely on server failure; applications hang but resume seamlessly on recovery. (Chapter 10)
Counterpoints
"Depth is more important than length: first make functions deep, then try to make them short enough to be easily read. Don't sacrifice depth for length." (Chapter 9) Five sequential 20-line blocks doing independent work in a single method are better left together than split into five shallow methods.
A network module that silently discards all errors makes robust applications impossible. "Masking is only valid when the handler genuinely resolves the condition." (Chapter 10)
Developers habituated to succeeding with first ideas resist designing it twice. "It isn't that you aren't smart; it's that the problems are really hard!" (Chapter 11)
Key Quotes
"The decision to split or join modules should be based on complexity. Pick the structure that results in the best information hiding, the fewest dependencies, and the deepest interfaces." -- John Ousterhout, Chapter 9
"The best way to eliminate exception handling complexity is to define your APIs so that there are no exceptions to handle: define errors out of existence." -- John Ousterhout, Chapter 10
"Code that hasn't been executed doesn't work." -- John Ousterhout, Chapter 10
Rules of Thumb
- Combine pieces that share information, are always used together, or cannot be understood independently
- Length alone is rarely a good reason to split a method; depth matters more
- Before adding an exception to your interface, ask whether you can redefine semantics so the condition is not an error
- Use exception masking when a low-level handler serves many callers
- Use exception aggregation to route many error types to a single well-tested handler
- For truly unrecoverable errors, crash with diagnostics rather than adding complex recovery logic
- Design it twice: sketch at least two radically different approaches before committing
Related References
- Comments as a Design Tool - Comments-first methodology catches boundary and error-handling design problems early
- Implementation Playbook - Practical sequences for applying these principles during modifications
- Naming, Consistency, and Obviousness - Naming difficulty signals that module boundaries are drawn wrong