
That’s not an abstraction, that’s just a layer of indirection
If you’ve ever worked on refactoring or improving the performance of a software system, you may have encountered a particular frustration: a code base that abstracts too much. Code that looks neatly organized and modular often reveals a maze of layers of indirection. Performance is slow, debugging is a nightmare, and your CPU seems to spend more time running abstractions than solving actual problems. This brings us to an important realization: not all abstractions are created equal. In fact, many are not abstractions at all – they are just thin veneers, layers of indirection that add complexity without adding real value.
So what makes a good abstraction?
An abstraction is only as good as its ability to hide underlying complexity. Think of a really great abstraction like TCP. TCP helps us pretend that we have a reliable communication channel, even though it is built on top of the unreliable protocol IP. It takes on the complexity of error correction, retransmission, and packet ordering so we don’t have to. It does it so well that as developers we rarely need to understand its inner workings. When was the last time you had to debug TCP at the packet level? For most of us, the answer is no.
This is the mark of great abstraction. It allows us to operate as if the underlying complexity didn’t exist at all. We take advantage of the good stuff, and abstraction makes the hard stuff invisible and unforgettable.
opposite of abstraction
But what about bad abstractions? These “abstractions” don’t hide any complexity: they usually just add a layer whose meaning derives entirely from the thing it’s supposed to abstract. Think of a thin wrapper around a function that doesn’t add any behavior but adds an extra layer for navigation. You’ve no doubt encountered these classes, methods, or interfaces that simply pass data around, making the system more difficult to trace, debug, and understand. These are not abstractions; They are just layers of indirection.
The problem with layers of indirection is that they add cognitive overhead. They often come under the guise of flexibility or modularity, but in practice they rarely end up delivering these benefits. Instead, they make the code base more complex and more challenging to use, especially when you need to squeeze out more performance or fix bugs.
abstract actual cost
We like to pretend abstraction is free. We randomly add another interface, another wrapper, and before we know it, we have a whole stack of them. This kind of thinking ignores a basic fact: abstraction comes at a cost. They add complexity and often performance penalties.
Abstraction is the enemy of performance. The more layers you add, the further away you will be from the underlying metal. Optimizing code becomes an exercise in peeling back layer after layer until you finally start doing the real work. Each layer represents a mental and computational burden. It takes longer to understand what’s going on, it takes longer to find the important code, and it takes longer for the machine to execute the actual business logic.
Abstraction is also the enemy of simplicity. Every new abstraction should make things easier – that’s the promise, right? But the reality is that each layer adds its own rules, its own interface, and its own possibilities for failure. Rather than simplifying, these abstractions add complexity, making the system harder to understand, maintain, and extend.
All abstractions are leaked
There is a well-known saying: “All abstractions leak.” It’s true. No matter how good the abstraction is, eventually you’ll encounter a situation where you need to understand the underlying implementation details. This leak might be subtle, like when you’re trying to understand performance characteristics (what’s the big-O complexity here?), or it might be more obvious, requiring you to dig deep into debugging to find out why something isn’t working as in As expected. Good abstraction can minimize these situations; a bad one can turn every little mistake into a dig.
A useful rule of thumb for evaluating abstractions is to ask yourself: How often do I need to look at the underlying layer? Once a day? Once a month? Once a year? The less often you have to break the illusion, the better the abstraction.
Asymmetry of abstract costs
There is also a certain asymmetry in abstraction. Authors of the abstraction immediately enjoy its benefits – it makes their code look cleaner, easier to write, more elegant, or perhaps more flexible. But the cost of maintaining this abstraction often falls on others: future developers, maintainers, and performance engineers who must work with the code. They must peel back the layers, follow indirect clues, and understand how things fit together. They are the ones paying the real cost of unnecessary abstraction.
This is not to say that abstraction is bad – far from it. Good abstraction is powerful. They allow us to build complex systems without getting lost in the complexity. But we must realize that abstraction is not free. They have real costs in terms of performance and complexity. If “abstraction” doesn’t hide complexity but just adds a layer of indirection, then it’s not abstract at all.
Next time you abstract, ask yourself: does this really simplify the system? Or is it just another layer of indirection? Use abstractions wisely, and remember – if you’re not truly hiding complexity, you’re only adding to it.
© Fernando Hurtado Cardenas.RSS
2024-12-25 05:06:58