Pass-through methods and variables
Chapter seven opens with a red flag: different layers must have different abstractions, and if they do, the split into modules was wrong. The classic example of layering is the file system: blocks and inodes at the bottom, methods such as open for file access at the top; network protocols work the same way. Then come pass-through methods: a module repeats the signature, passes control downwards, holds no logic of its own, and the class becomes shallow. In the book's figure that is variant "a" with its stars, circles and pentagons; variants b, c and d show the cures: call the lower modules directly, redistribute the logic, or merge the modules. The author repeatedly says a big module is no disaster as long as it hides complexity behind a simple interface. The participants pick at the Russian rendering of "shallow": it brings size to mind, whereas the point is depth — what lies under the surface.
Not every repeated signature is bad: a dispatcher has logic of its own, picking an implementation and routing the call down. Decorators, though, are overused, the author warns, and he offers questions for self-testing: could the logic go in directly, into the calling code, or into a separate standalone class? The participants like that the author gives a way of reasoning rather than a rule; scenarios for decorators remain — an external class you cannot modify, or interface translation. A separate point: the abstraction of an interface must differ from the implementation, otherwise the implementation leaks out and the module stops being deep. The example is a text editor where the author's students built a line-oriented API rather than a character-oriented one, because storing text that way is simpler inside — even though insertions happen at arbitrary places.
Complexity downwards and the cost of splitting
The end of chapter seven deals with variables threaded through every layer: the IDE will happily add the parameter to the whole call chain, and it is very easy to fall for. The author admits he knows no ideal solution: a container object shared across layers, a global variable, or a context — ideally immutable, so that concurrent access does not become the next problem; the context is the best thing he has seen in practice. Chapter eight boils down to pulling complexity downwards: a module usually has more users than developers. What pushes it upwards is exceptions and configuration, where the user tunes parameters he may not understand and the combinations multiply so fast that hardly all of them are tested. Configuration and exceptions are precisely the implicit interface: a formal signature plus an undocumented tail. Handlers are barely tested either — hence the quote that code which has never been run does not work.
Chapter nine, better together or apart, is presented by Oleg. Splitting adds complexity by itself: cutting a method by the cargo-cult rule of twenty lines multiplies entities; sawing a monolith into microservices relocates complexity into operations rather than removing it; inseparable pieces of code get duplicated, so changes have to be made in several places. The red flag for a boundary: one module cannot be understood without reading another's implementation. The author warns against cutting an elephant into small elephants and suggests putting the pieces back together. A participant recalls a colleague who rewrote a system without really grasping the domain and cut microservices by the number of the main domain models; transactions between them turned out to be necessary, he left the company, and the team glued the ten or so services back into three along clearer domains. Merge when the pieces share information, are used together, are united by a higher-level concept, or make no sense apart.
Uncle Bob, exceptions and designing twice
A separate thread is go to: normally a red flag, yet in the Linux kernel, as the participants remember it, it jumps into the section that releases resources correctly — and the author advises exactly that, collecting identical handlers in one place. The taboo goes back to the Fortran era, and someone drops a 1968 article into the chat: a rule without an explanation gets repeated unthinkingly. At the end of the chapter the author argues with Uncle Bob openly for the first time: an alternative path exists, but its metrics are rigid, obscure and do not reduce complexity. The participants enjoy that he states someone else's position, then his own, and explains why — closer to an academic than to selling a book. Chapter ten is about defining errors so that they do not exist. Throwing an exception is easy; deciding what to do with it is already cognitive load — handle it here, pass it upwards, or abort the operation and risk leaving the system inconsistent; Java's checked exceptions force client code to act at the API level. A telling pair of examples is deleting a file: on Unix you delete it and go and rest, on Windows it may come to rebooting.
The author also recalls the language he designed: unsetting a variable threw an exception when the variable was absent — though people called unset precisely to clean up state, so the errors had to be caught and silenced. Had the semantics been "make sure the value is gone", the work would already be done. Oleg adds masking: a protocol retransmits the part of a packet that never arrived, retries within reason settle a network error on the spot, and a network file system would rather wait than fail straight away — the client can only hit refresh anyway. Then aggregation: let exceptions travel up and handle them in one place — formally against the rule about not pushing complexity upwards, but easier to control from above; when memory cannot be allocated the author advises simply crashing. Chapter eleven, design it twice, one of the hosts takes personally: at school he solved problems on the first attempt, up to olympiad level; at university nothing worked without a systematic approach. The algorithm: gather alternatives, list the pros and cons, assemble a combined option if needed, and otherwise step back and search again until the solution is merely good enough — a continuation of the advice to spend 10% of the time on design. Half the book is done; in a week the club takes on comments in code and documentation.
What to take away
- 01The same abstraction on different layers is a red flag: a module that only forwards a call hides nothing; the cures are calling the lower module directly, redistributing logic, or merging modules.
- 02There is no ideal way to thread a variable through layers: a shared container object, a global variable, or a context — the author calls the context the best he knows and prefers it immutable.
- 03Splitting relocates complexity instead of removing it: microservices cut by the number of domain models required transactions between themselves and were merged back into three services along clearer domains.
- 04The best exception is the one that never happens: retries and waiting instead of an error, aggregated handling in one place, and simply crashing when memory runs out, with a fast start instead.