The Cost of Abstraction
When I was a junior engineer I thought the goal of programming was to write as little duplicated code as possible. If I saw three lines that looked similar in two places, I’d extract a helper. Two helpers that took similar parameters? A class. Two classes with similar interfaces? An abstract base class.
I now believe most of those abstractions made the code worse.
What an abstraction actually buys you
An abstraction is a contract. You’re saying: “I promise you can use this thing without thinking about what’s inside.” Every abstraction is, in essence, a small lie — and the value of the lie is proportional to how much complexity it lets you forget.
But abstractions also have costs that nobody mentions:
- Indirection cost. Every abstraction is a layer your reader must trace through.
- Generalization cost. To serve two callers, the abstraction must handle the union of what they need.
- Wrong-fit cost. When a new caller doesn’t quite fit the existing shape, you either bend the caller or distort the abstraction.
Wrong-fit cost compounds. Each time you force a new caller into an abstraction that wasn’t quite designed for it, the abstraction gets a little uglier and the next caller has to fight a little harder.
The “rule of three” is wrong
The conventional wisdom is: don’t extract an abstraction until you have three callers. I think the real rule is closer to: don’t extract until you have three callers AND you can clearly see what they have in common at the level of meaning, not just shape.
Three things that look similar are often not actually similar. They just happen to share some lines of code. If you extract them too early, you’ve welded them together for no reason, and now changing one means changing all three.
When abstractions are worth it
Abstractions are worth it when:
- The thing being abstracted is stable — it represents a concept that won’t change much over time
- The callers are genuinely interchangeable — they could each be swapped for the other and the abstraction wouldn’t notice
- The cost of the layer is paid back many times — by readers, by future implementers, by callers who would otherwise have to know the underlying details
Most of the abstractions I extracted as a junior met none of these. They just looked clever.
These days I duplicate code more than I used to. When I do extract an abstraction, I’m often surprised how much work it takes to make the seams clean. That work is the abstraction earning its keep.