In object-oriented programming, it is common to extract a superclass when two classes share some common behavior. There are other ways to structure the code, such as using composition and delegation, but for this post, I’ll use inheritance.

Simple Inheritance Hierarchy
class Guide
attr_reader :answer_to_life
def configure(options)
@answer_to_life = options.fetch(:answer, 42)
end
end
class Restaurant < Guide
def configure(options)
super
@location = options.fetch(:location, :end_of_universe)
end
end
class Gratitude < Guide
def configure(options)
super
@thanks = options.fetch(:thanks, :fish)
end
end

This is a pretty standard style of inheritance hierarchy, and we learn to write code like this very early on. However, there’s brittleness here, even though it may not be that obvious.

A new feature request comes in that requires another subclass in this hierarchy:

New Subclass
class Summary < Guide
def configure(options)
@harmless = options.fetch(:harmless, :mostly)
end
end

Aaaaannd, I just introduced a bug. Did you see it?

The Bug
summary = Summary.new
summary.configure({})
summary.answer_to_life # => nil # WHAT!?!?

All of the Guide subclasses are supposed to know the answer to life, the universe, and everything. Why doesn’t Summary?

The answer, of course, is that I forgot to send super in the new configure method. This is an easy mistake to make, but of course there are tests to catch things like this, right? Right?

I could leave things in this state, beat myself up for making this simple mistake yet again, and move on to the next feature.

There is another way. I will apply some Poka-yoke. Poka-yoke is a Japanese term meaning “mistake-proofing”, and it is well-known in lean manufacturing. It’s a good technique to apply any time we find ourselves making the same little mistakes over and over again, like forgetting to send super.

In this situation, I can apply the Template Method design pattern from the Gang of Four book:

Refactored Version
class Guide
attr_reader :answer_to_life
def configure(options)
@answer_to_life = options.fetch(:answer, 42)
configure_details(options)
end
def configure_details(options)
# Subclasses may want to do more here
end
end
class Restaurant < Guide
def configure_details(options)
@location = options.fetch(:location, :end_of_universe)
end
end
class Gratitude < Guide
def configure_details(options)
@thanks = options.fetch(:thanks, :fish)
end
end
class Summary < Guide
def configure_details(options)
@harmless = options.fetch(:harmless, :mostly)
end
end

This fixes the bug and makes it much harder for my teammates and I to introduce a similar bug later.

In Practical Object-Oriented Design in Ruby: An Agile Primer, Sandi Metz calls this a hook method, and distinguishes the technique from Template Method, which she also discusses. She also raises a very interesting point:

When a subclass sends super it’s effectively declaring that it knows the algorithm; it depends on this knowledge.

I hadn’t thought of it this way before, but it’s a fascinating thought.

Whatever you call it, it’s a handy way to make code a little less brittle.

Afterword

I wrote the original draft of this post several days ago. Just before I was going to post it, Sandi Metz tweeted a link to another article that makes this same point, but in a broader context. It’s an interesting read.