“Clean Code” Book Club: Chapters 10–13

Posted on Wed 11 September 2024

Continuing with our book club on Robert Martin’s “Clean Code”, in July and August we’ve discussed chapters 10 (“Classes”) to 13 (“Concurrency”). (As usual, this post collects most of my notes and some points from our group discussion; completeness is explicitly a non-goal.)

Chapter 10: Classes

This chapter was written by Robert Martin together with Jeff Langr.

The first rule of classes is that they should be small. The second rule of classes is that they should be smaller than that. (p. 136)

That’s a less than auspicious way to start the chapter.

However, instead of using lines of code, this chapter uses responsibilities of a class as a measure. This is far more reasonable, since it’s actually related to code cleanliness. As a guideline, it cites the Single Responsibility Principle:

a class or module should have one, and only one, reason to change. (p. 138)

That is good advice in principle; but once again, we find ourselves struggling with a philosophical question: What is one reason? How fine-grained do we define our concepts? The authors advocate for a very fine-grained approach, using the metaphor that we want our tools neatly organized into toolboxes instead of in a single jumbled pile. However, I’m wary of overdoing it, since a proliferation of toolboxes can become overwhelming, too! (Imagine if—instead of a drawer for kitchen utensils—your kitchen had separate drawers for whisks, for slotted spoons, for mixing spoons, for potato mashers, for ladles, for spatulas, for measuring spoons, for skimmers, for graters, etc.)

Cohesion is suggested as a useful indicator: High cohesion indicates that all parts of a class operate together (or “change for the same reason”), while low cohesion indicates that a class might be split into multiple, more cohesive parts. This discussion made me wonder whether there are tools to visualize code cohesion. (Though if we make our classes as small as the authors would like us to, it’s probably fairly easy to determine the cohesion with a quick look over the code.)

Also, there were several editing issues in code listing 10-10 (for example, the order of subclasses differed from order of functions in 10-9, for no discernible reason; and it looks like a few lines in the PlaceholderList and PrepareInsertSql classes were deleted accidentally, so both classes got smushed into each other).

Chapter 11: Systems

Since the contents were highly Java-specific and not transferable to any other languages, we skipped this chapter in our book club. (Several of us noted that this chapter felt very out of place in the book, both in terms of writing style and target audience.)

Chapter 12: Emergence

This was a nice summary of many key aspects from previous chapters, so we didn’t have too much to discuss. I found it a little strange to have such a chapter located at this point in the book—it might have worked better to swap this with chapter 13, to close the first part of the book with this summary chapter, before getting into the worked examples in chapters 14–16.

Chapter 13: Concurrency

In this chapter, Brett L. Schuchert took up the unenviable task of summarizing concurrency in 13 pages (plus a 30 page appendix with more detailed code examples). Due to this short length, the chapter felt lacking in several areas: for example, there was no discussion of the distinction between multithreading and multiprocessing; and subsections on different execution models (Producer-Consumer, Reader-Writer, Dining Philosophers) or writing correct shut-down code contained brief descriptions of the problems, but no actionable advice. Both the Java-specific focus of the book and its age introduced further gaps, since many concepts or approaches that are more prominent in other languages or simply younger than ~15 years (async/await, promises and futures, actor model, …) were missing completely.1

Those inherent limitations aside, the chapter did a good job dispelling some myths and misconceptions around concurrency. I especially liked the example demonstrating (including the full byte code in the appendix!) how even a simple line of code like ++lastIdUsed; is non-atomic and can lead to threading issues.

Concurrency Defense Principles

Referring back to the Single Responsibility Principle (“A method/class/component should have a single reason to change”), this subsection highlights that concurrency design is a reason in its own right. Code to manage concurrency should thus be separated from domain-specific code. The three corollaries discussed here (limit the scope of data, use copies of data, threads should be as independent as possible) effectively lead towards a functional programming style; though sadly the author did not explicitly mention this.

Testing Threaded Code

To me, this subsection was a highlight of the chapter, containing clear descriptions of the problems in testing multithreaded code and actionable advice on solving them:

  • Treat spurious failures as candidate threading issues
  • Get your nonthreaded code working first
  • Make your threaded code pluggable
  • Make your threaded code tunable
  • Run with more threads than processors
  • Run on different platforms
  • Instrument your code to try and force failures
  1. That’s before we get into implementation details of individual languages, other than Java. E.g. most relevant to us, Python has a couple peculiarities like the Global Interpreter Lock (which enforces that only one thread at a time executes Python bytecode) and how it interacts with binary extension modules like NumPy.