The right abstraction
You’ve probably heard the saying, “prefer duplication over the wrong abstraction”. It’s one of those things people throw at you to look smart. And it’s obviously, irrefutably true because it’s self-proving. Anything is better than “the wrong thing,” by definition.
Throw this quote at any developer with the slightest hint of imposter syndrome and you’ve won your case already — not because you’re right and not because you actually understand the problem or articulated a reasonable objection, but because you used intimidation, name-dropping, and logical fallacies to get your way.
The assumption inferred from the prefer duplication principle out-of-context is if you don’t know for sure that you’ve found the right abstraction forever, you probably have the wrong one and in any case it’s not worth the risk, so you should throw out your abstraction altogether and prefer duplication instead. This is a grave mistake.
If an abstraction fits your needs right now, it’s as right as it can be right now and you should use it without hesitation. There’s no way of telling if an abstraction is going to be right in the future — if and when your requirements change — so there’s little point thinking about it.
If you actually read Sandi Metz’ on this, she never argues against doing the abstraction in the first place. Instead, she argues that we shouldn’t continue using an existing abstraction that’s no longer fit for purpose.
Once an abstraction is proved wrong, the best strategy is to re-introduce duplication and let it show you what’s right. — Sandi Metz
On that point, she’s absolutely right. We shouldn’t bend over backwards to fit an existing abstraction to new needs just because it exists.
What is abstraction?
Abstraction isn’t really about reducing duplication — though that’s often the result — it’s about separating concerns and exposing a simple interface that puts the essential ideas and intentions on display. It’s breaking down and modelling a problem in the simplest way possible so that when you or the next developer comes to read the code, they can understand it from the outside. Ultimately, it’s about making your software easier read, easier to reason about, and easier to change.
You don’t need to do a certain amount of duplication before you’re allowed to come up with an abstraction, either. It’s totally reasonable to plan ahead and design something.
No amount of hitting a tree with a blunt rock will ever lead you to designing an axe or a chainsaw. If you know you need to fell a bunch of trees, it’s your responsibility to either find or design a tree-felling device first.
For each desired change, make the change easy (warning: this may be hard), then make the easy change. — Kent Beck
This, in my view is the golden rule of good software development.
First, think about how you wish you could solve the problem — perhaps even write the code you wish you could write. Then, find a way to make that work and do that first. Only once you’ve made the change easy should you make the easy change. Design the chainsaw, build the chainsaw, use the chainsaw.
You might have heard a different variant of this principle: “make the change easy, then make the easy change” — developers are awfully prone to forgetting parenthesis — but this simplification misses a crucial point: making the change easy might be really, really hard! It might involve refactoring old code no one’s touched in a while. It might even involve macros and meta programming. Simple doesn’t equal easy. But I promise it’s worth the effort in the long run. The thing about abstractions is they can always be shared and re-used and improved upon.
So abstraction is okay?
Yes. Not only is it okay, I would argue it’s the keystone of good software engineering. Without abstraction, we’d still be writing all our programs in 0s and 1s because even basic logic gates are themselves abstractions on binary.
In its simplest form, abstraction is taking a concept — some data or a calculation — and giving it a name or signature, allowing you to engage with it at a high level.
Jason Swett used an excellent analogy:
Let’s say I go to McDonald’s and decide that I want a Quarter Pounder Meal. The way I express my wishes to the cashier is by saying “Quarter Pounder Meal”. I don’t specify the details: that I want a fried beef patty between two buns with cheese, pickles, onions, ketchup and mustard, along with a side of potatoes peeled and cut into strips and deep-fried. Neither me nor the cashier cares about most of those details most of the time. It’s easier and more efficient for us to use a shorthand idea called a “Quarter Pounder Meal”. — Jason Swett
If you haven’t already, go read his wonderful piece on Abstraction in Rails.