Book Review: Practical Object Oriented Design in Ruby

A seasoned Ruby Programmer shares her guidelines for agile object-oriented programming

Posted by Grace on November 4, 2021

Header image source: LoveToKnow

POODR is a book about how to make good object-oriented code under an Agile process where you don’t know all the rules in advance. The author, Sandy Metz, has a conversational and approachable style to her writing. Metz’s philosophy - which she makes a very good argument for - is that there’s no one right way to do everything. But depending on the situation, there are definitely more appropriate design choices to be made.

A recurring theme in the book: Don’t try to anticipate the future. In an Agile process, change is the only constant. If you make assumptions about the future and incorporate those into your code, you will get yourself in trouble when the requirements change after a few sprints and you have to retool your code. The solution? Make sure methods have only one responsibility and are able to be moved from class to class if necessary.

When it comes to Agile development, managing dependencies can be a delicate process. Dependencies are when an object knows the name of another class, the arguments it requires, or otherwise depends on a class outside itself. Dependencies can be okay, but if the classes are likely to change and those changes will break the dependents, the codebase becomes fragile and inflexible.

By using dependency injections, you can avoid calling classes names within other classes (which will break if the class changes) and keep better track of what other objects are used by the object in question.

In order to create a flexible structure, objects should be as much like “black boxes” as possible, with predictable inputs and outputs. Their inner methods and processes should not need to be viewed and changed by other objects. Metz talks about public vs private interfaces. The “public” interface of an object is what other objects can invoke and depend upon. This should be the main responsibility of the class, and there shouldn’t be too many. The private interface is interior and can change as long as the public interface is still dependable.

The Law of Demeter, despite its name, is more of a suggestion for most cases. The Law of Demeter suggests that you shouldn’t know too much about other objects’ inner workings. “Use only one dot” is a simplified way to think of this - while you might invoke Object().method but you shouldn’t be invoking Object().item.attribute.method. The method being invoked is too far away, the code is too complex. However, this is just a rule of thumb that doesn’t always apply.

Metz also discusses Polymorphism. This is the idea that many different objects can respond to the same calls. One concept that uses polymorphism is Duck typing. Duck types are methods that are more defined by their behavior than by their class. They are public and aren’t tied to one class. Instead, they can be used by multiple classes that have the same functionality and needs. It doesn’t need to know everything about the class as long as the part of the class it interfaces with behaves as expected.

Inheritance, when objects inherit from each other and become more specific, is a powerful design technique. However, when you design hierarchies to be extended in the future, you need at least some domain knowledge so you can write with the future in mind. This may not always be possible in Agile programming. If you are using inheritance, there should be a superclass that is general enough for all the specific instances to inherit from. All the subclasses should be able to do everything that the superclass does.

When the programmer lacks the foreknowledge to predict which hierarchies will work for Inheritance, they can instead use Composition. Composition is when you have multiple smaller parts that don’t depend on each other as much. The overall design can be much more complicated and hard to understand when things are done this way. However, keeping classes extremely small and specific makes things more flexible in the future. Composition-based design can be rearranged easily, whereas inheritance has more rigid hierarchies.

The overall sentiment of the book is that in order to write good code, you should always keep the future in mind and keep your options open. As Metz puts it: “Because no design technique is free, creating the most cost-effective application requires making informed tradeoffs between the relative costs and likely benefits of alternatives.” In order to make these design calls, the programmer needs to have a good sense of what she knows and doesn’t know about the future of the codebase. This book gives programmers helpful, practical frames to think about the decisions they have to make.