Sandi Metz, Practical Object-Oriented Design in Ruby (Book Review) by jgn on Sunday, November 25, 2012 in Reviews, Ruby, and Code

It's been awhile since I've reviewed a book (here are some:, but Sandi Metz's new one is so good I need to unburden myself.

Sandi Metz's Practical Object-Oriented Design in Ruby: An Agile Primer ( Amazon) may go down as one of those slim classics like The C Programming Language that people actually reread occasionally to learn something besides the obvious "content" of the book.

The book provides definitions and rules for the basics of object-oriented design: She provides clear and succinct definitions of SOLID, the Liskov Substitution Principle, the Law of Demeter, etc. These well-known design principles are supplemented with the author's own rules, such as: "Faced with a problem that composition can solve, you should be biased towards doing so" (p. 184); "Construct public interfaces with an eye toward minimizing the context they require from others" (p. 79). All of that is extremely useful for the novice, and the table of contents and index make this book robust enough to serve as a brief reference.

But the title doesn't lie: It is indeed "practical" and a "primer." The word "primer" has a boring definition which is "an elementary textbook," but the more provocative definition typically involves "teaching children to read." How do you teach children to read? Through stories. This is where the book really shines. If you skim down the table of contents, you'll see that almost every subsection is verbal. For example, from Chapter 5: "Understanding Duck Typing"; "Overlooking the Duck," "Compounding the Problem," "Finding the Duck," etc. This is a powerful and almost surely a deliberate strategy: Every section is about "doing" something. If you read these sections in order, you will find that when she does get to sections that do provide rules ("Recognize the Antipatterns", "Insist on Abstraction," "Honor the Contract" [pp. 158-161]), they are fully justified through the narrative experience of code transformations. I like that. The sections are small enough that if you want a refresher, you won't have to read a lot of story to get to the moral. Metz has managed to cut out all of the flab that makes so many narrative tutorials so boring. Indeed, they are short enough that someone who already knows the material may reread the sections on, say, object composition, just to see how Metz works it out.

Metz writes with the voice of god but is also a judicious god and lets the reader know that solutions are contingent on the requirements of time and money. Even when she is most vociferous insisting: "The rules-of-thumb for testing private methods are thus: Never write them," she'll continue: "and if you do, never ever test them, unless of course it makes sense to do so. Therefore, be biased against writing these tests but do not fear to do so if this would improve your lot" (amen, sister). God can tell you not to eat the apple, but she also gives you free will so that you can learn and grow from your own experience.

I have one tip for novice readers of this book: When you get to the end, on page 241, please read the book again from the beginning. Metz leaves the chapter on testing until the very end. From personal experience, I have learned that it is almost impossible to teach object-oriented design simultaneously with testing. But after reading Chapter 9, on testing, I think you will reread Chapters 1-8 with new relish. Indeed, I think a nice challenge for the reader would be to write tests or specs for the earlier material, in light of her counsels on testing in Chapter 9.

The book is not perfect. I think there is room for improvement for a second edition. The first thing is that the book needs an annotated bibliography or some kind of guide for future reading. The reason for this is that since Metz writes with the voice of god and makes her case through her narratives, she has a lot of well-earned authority. While she does cite in passing the authors of particular gems (e.g., MiniTest and RSpec on p. 199), I think the reader is starving for advice on "what to do next." One can say "oh just Google it," but that's really the problem since a search engine will bring up so many options. Additionally, I think the book should recognize that it represents a point in time. Providing a list of resources anchors the book in our current historical situation as software developers.

My other concern regards Chapter 4. Chapter 4 may be the most important chapter in the book, because it shows how a new class is extracted from a group of classes with messy overlapping concerns -- this goes right to the heart of the "design" question proposed by the book's title. In my experience, class extraction is one of the harder things for novice developers to understand and practice -- I'm still messing it up. Typically when class extraction is discussed in other books, the assumption is that the smell is obvious, or that the programmer already knows why the class smells. I think such "smell certainty" is far from obvious, and owing to Metz's style she can make the case elegantly through stories. The problem, though, is that Chapter 4 has only sequence diagrams and no code. (It's great that there are sequence diagrams: They are neglected in the Ruby world.) One of the things the sequence diagrams does in passing is introduce a class method (Trip.suitable_trips - see p. 66).

The only other place a class method has been used before this point is in Chapter 3, with regard to a module. So what's going on here? (Of course I know why: It's a finder method on something like an active record class.) The class method is preserved here even in the solution on p. 75. Might I not have a class hierarchy for Trip? Why is this a class method? What is a class method? To be sure, the Trip class is really just in a support role, but I think that absence of code means that the novice reader is going to have a hard time seeing the reality of what is being worked up. I'd also like to see how TripFinder is constructed: The text tells us that it will "embody the rules at the intersection of Customer, Trip, and Bicycle" - do they all get injected at object instantiation? All of this would be resolved with some code examples. They wouldn't even have to be quoted; they could just exist in the download. (As an aside, I also find it confusing that the the class name Customer is below the instance name ("moe"), while this order is effective reversed for Trip.)

One of the things the book does well is to start off with plausible but screwed up class organizations (elsewhere she calls them "booby traps" - pp. 127, 132) which are then cleaned up over the course of chapter. That's fine, but I think there is an opportunity to return to the beginning and ask how it started on such an awkward footing. The chief finding of Chapter 4 is that the missing class is something called a TripFinder (p. 75). That class is all about responsibility. But what is the use case that started the chapter? Here it is: "A Customer, in order to choose a trip, would like to see a list of available trips of appropriate difficulty, on a specific date, where rental bikes are available" (p. 64). It is interesting to note how this use case diverges from recent thinking about feature definition. As the Cucumber Book has it, "In order to 〈meet some goal〉 As a 〈type of stakeholder〉 I want 〈a feature〉" (p. 31). They go on: "By starting with the goal or value that the feature provides, you’re making it explicit to everyone who ever works on this feature why they’re giving up their precious time. You’re also offering people an opportunity to think about other ways that the goal could be met." Because the goal is given first ("in order to meet some goal"), and the encouragement is to think about " ways that the goal could be met," I think that the developer is going to be pushed to think about responsibilities earlier. By making "customer" the subject of the sentence, it is no wonder that the novice's code starts off with such an impoverished sequence diagram: There is little in the original statement of the use case that makes the key responsibility -- trip finding -- pop out. At the end of this section, I think Metz might return to the original use case, and critique its construction. My bet is that the novice would have been more successful with: "In order to choose a trip, as a customer, I want a list of suitable ones based on difficulty, date, and bike availability." Now "choosing a trip" comes first.

But these are minor concerns of a sustained effort that provides great value. I can't wait until the next book from Sandi Metz.

comments powered by Disqus