Lessons from Sandi Metz
6.20.2020
I had the opportunity a couple of months ago to participate in a (remote) workshop led by Sandi Metz. I had previously read and really enjoyed Sandi's excellent book, Practical Object-Oriented Design in Ruby. The course, which was a few hours per day for 5 days, covered a ton of ground, but I wanted to jot down a few of the lessons that really stood out to me. I had hoped to write this sooner, but well, you know how it goes. I’m going to write in broad strokes here, as this post will mostly just serve as a reminder to myself. If you want to go deeper, get one of her books!
Polymorphism
If I had to pick a theme from the week, it would be the wonders of polymophism. I think in Rails-land, it's a bit too easy to forget that polymorphism is useful at far more than just the model level. In a perfect world, objects would know almost nothing about their dependencies, other than that they can receive certain messages, and type checking (e.g. if foo.is_a? Bar
) would be very rare.
In serving the goal of just calling methods on objects, whatever they are, we spent a good chunk of time talking about dependency injection. So instead of your class knowing the names of any other classes, it can receive a specific class as an argument, and just know that that class will respond to .some_method
. This concept can extend to tests; you can inject fake classes that conform to the interface that your “real” classes will use.
Primitive Obsession
This was another interesting topic, and it was a great illustration of the benefits of polymorphism. Basically, programmers love to use the “primitives” of their languages as forks in the road, e.g. if foo == 1, then etc…
. Ruby doesn’t really have primitives (“Everything is an object!”), but we all have a sense of what this means: numbers, strings, booleans, etc. A good pattern to establish is to wrap these primitives in your own custom classes, and then send messages to those classes. So a big conditional for a bunch of primitive value checks could turn into a one liner, some_number_class.method_to_return_value
.
Procedures vs. Object Oriented Design
One general point that really hit home for me was that breaking a procedure or script into objects does not necessarily make it object-oriented. If you have a disgusting, 500 line script where Thing A happens, then Thing B happens, then Thing C happens, it's certainly not a bad thing to make those parts more explicit and isolated, especially for readability purposes. But you haven't really made the objects behave like general plugs and adapters - you're just moving code around. This was a great reminder for me to hear, because I do this kind of refactoring a ton. Sandi believes that there is a place for well-implemented procedures, but it shouldn’t be mistaken for object oriented design just because there are custom objects involved.
Shameless Green & Turtles All the Way Down
I won’t go into much detail, but there are a couple of caveats to these lessons.
The first is that despite well crafted object-oriented design being a great goal, often times (most times?) our first step should be to get to what Sandi calls “shameless green.” This is simply the naive solution to a problem that makes the tests pass. And without an extra use case, this solution can often be good enough. This is a good place to start (and maybe stay) because refactoring into a more ideal situation later is pretty much impossible without tests that break when they should. This was a good reminder to me, because I have been burned by premature over-optimization many times.
It’s also important to note that while many of the tricks we learned were focused on removing complex conditional logic, at a certain point, your program does need to decide which objects it will be working with. It’s not turtles all the way down. Sandi recommends object factories for this purpose. There are ton of ways to develop these factories, which I won’t get into, but suffice to say, deciding in one place what sort of object you’ll be dealing with (and then sending the chosen one messages) is greatly preferable to deciding in a dozen places how to deal with something based on its type.
Teaching
One (paraphrased) quote I've always really liked is "If you can't explain something simply, you don't understand it well enough." After taking the course, it's pretty easy to engage in some hindsight bias and think, "Well, of course (insert-point-about-object-oriented-best-practices-here)." But how clearly could I actually have articulated some of these points? Sandi's ability to convey complex topics concisely, but with the necessary nuance, and with the upmost clarity, demostrates her absolute mastery of the subject matter. As great as it was to gain the knowledge that the course provided, it was also enjoyable to watch a gifted teacher at work. And I would be remiss if I didn't also shoutout her excellent teaching partner, TJ Stankus, who was also a tremendous instructor.
Other Posts
The Best Projects Can Be Done in a Weekend
Everyone Has Something To Offer
Book Thoughts: Capital and Ideology
Naive Diffie-Hellman Implementation in Ruby
When Does the Magic Comment Work, and When Does it Not?
Benchmarking Arrays Full of Nils
Go, and When It's Okay to Learn New Things
Grouping Records by Month with Ruby
Add Timestamps to Existing Tables in Rails
The Busy and (Somewhat) Fit Developer
TuxedoCSS and the Rails Asset Pipeline
Gem You Should Know About: auto_html
Querying for Today's Date with ActiveRecord