Travis Emmett's blog

A coder in training's thoughts

Sandi Metz and Object-Oriented Design in Ruby

If you talk to most Rubyists, a book almost universally held in high esteem is Sandi Metz’s Practical Object-Oriented Design in Ruby. Before my time at The Flatiron School, I had some experience in programming but mainly from the procedural side, so when Objects or Classes started coming into the fold, I would usually end up running for the hills. After the program, I felt like I had finally acquired a solid foundation in Object Orientation, but I still wanted to get a better understanding of some of its nuances. So I thought it was high time to pick up Sandi’s tome and see what all the fuss was about. And, about a quarter of the way in, I get it.

It’s very cool having a seasoned developer like Sandi describe, in fluid prose, her best practices over the last 30 years of her career. It’s like having a good friend available at all times to answer your nagging questions “Ok sure, but why?” It’s also great to see, rather than just teaching from the best code first, her progression through various refactorings starting with the newbie antipattern. So without further adieu, here are some interesting points from her from the first few chapters:

Classes and methods should have a single responsibility and do the smallest possible thing

Most people have heard this before but it’s important because one of the major advantages of Object Oriented code is that if written correctly it should serve as a set of interchangeable parts to be used by future programmers. Implemented well it usually means features can be changed, removed, or added without changes cascading down through your application’s codebase in a programming nightmare. D.R.Y. (Do not Repeat Yourself) ties in nicely here because achieving DRY code often forces you to break your program into small, unique parts.

Deciding on how to divide up your classes and what responsibilities can be intimidating but Sandi provides a few rules of thumb: 1. Try to define the purpose of your class in one sentence not using AND or OR. If it doesn’t fit, probably rethink it 2. As for what behaviors belong in your class, try asking your class its various methods in the form of a question to see if it makes sense. Like “Hi Appointment, what is your time?” sounds pretty good. But “Hi Appointment, what is your square footage?” sounds suspect and belongs in another class.

Avoid dependencies

Avoiding dependencies is a large topic (too large to be covered fully here) and is one of the central theme’s of Sandi’s book but I think this statement of hers sums it up nicely:

“Every dependency is like a little dot of glue that causes your class to stick to the things it touches. A few dots are necessary, but apply too much glue and your application will harden into a solid block.” -Sandi Metz

So very true and I couldn’t have said it better. Minimizing dependencies between classes ensures that when one class needs to change that it will be pliable and won’t set off a cascade of expensive changes in other classes. One small way to do this that I enjoyed reading a little more about was why it can be advantageous to use a singular hash argument when instantiating an object instead of relying on several argument order ones:

1
2
3
4
5
6
7
8
9
10
11
Class Person
  attr_accessor :name, :height, :hair_color

  def initialize(args)
    @name = args[:name]
    @height = args[:height]
    @hair_color = args[:hair_color]
  end
end

Person.new(name: Travis,  hair_color: brown, height: 6.25)

Using a hash has several advantages including 1. As an initialization argument’s order and number can sometimes change in the future this helps prevent it from breaking when other classes instatiate a new object of that class 2. The keys are instructive of their purpose and what their values represent. 3. Why, removes unnecessary dependencies across your code of course!

Heed the Law of Demeter (LoD) aka Avoid Trainwrecks

The Law of Demeter broadly maintains that it is bad practice to send messages to distant objects when relying an intermediate object to get there. Every Demeter infringement isn’t necessarily wrong but always be wary of the possibility especially if you are altering an attribute of a distant object and not just accessing it. Following this advice results in loosely coupled objects, a hallmark of good OO code.

Take for example if you have a class called “Order” and it has a method within it called “place_order” and within that method the following call is made: customer.cart.items.update_stock. This would likely be a violation of Demeter because you are accessing the customer object to get to the cart object to get to the item objects to implement the update_stock method to change each item’s quantity attributes. Whew, I’m outta breath. Sometimes this is simply referred to as “avoiding trainwrecks” due to a violation’s long method chain’s resemblance to train cars and their propensity to crash your program, which I think is much more memorable than remembering the Greek harvest goddess (sorry Dem).

Access instance variables via methods

Yes, I am guilty sometimes of referring to instance variables directly when I should use a reader or writer method instead. But I never fully understood why it was important not to. Sandi makes a good argument for always using a reader method instead because if you decide that your instance variable requires an interim calculation after you have peppered the instances throughout your codebase, it will take some time to fix when using a method would require a code change in only one spot.

Actively categorize your methods as public or private

How you classify your methods within a class tells a story about your code and communicates your intentions to other programmers. A public method generally carries along the following assumptions with it: 1. Other objects are free to utilize it 2. That it is stable and will not likely change 3. Communicates the responsibilities and purpose of your class. 4. That it should be tested. Private methods on the other hand generally indicate that your code is not meant to be relied on by other objects (it actually cannot in Ruby without a workaround) and may change and generally is not part of the core testing framework of an application.

Well, these are just a few of the many things I have learned thus far from Sandi and look forward to finishing the rest. It really is a fascinating book because it delves so deeply into the philosophy of programming and object orientation. Perhaps I will go through some other lessons of hers when I have the chance. Thanks for reading!