In Greppability is an underrated code metric, Moriz Büsing writes about the value of being able to grep strings, function names and patterns from a code base and how much harder it becomes when code is overly abstracted. For example, database table or column names are generated dynamically, never appearing in the code:
A better version would be:
since you can now grep for shipping_addresses
to find where that part of the code is executed.
It’s on a lower level of abstraction but makes the code much nicer to work with.
In That’s not an abstraction, that’s just a layer of indirection, Fernando Hurtado Cardenas writes about what makes a good abstraction and what becomes a layer or indirection.
One of the good abstractions he mentions is TCP which abstracts the complexity of setting up an unreliable connection away behind an interface with which we can pretend it is a reliable one.
Of the opposite, he writes about how adding layers of functions and classes to wrap code can cause indirections that don’t serve any value but makes navigating and understanding the codebase harder.
Often a good abstraction makes the life of a developer easier while a bad abstraction slows the developer down and makes changing the code harder. A couple of times in my life I’ve had to work with abstractions that I had real hard time following and many of those times have been with Redux and Redux Sagas. I understand how they work on a principle level but I often see them ending up as layers and layers of abstractions making it hard to figure out where to make changes or how to follow the flow when debugging.
I like his rule of thumb:
A useful rule of thumb for assessing an abstraction is to ask yourself: How often do I need to peek under the hood? Once per day? Once per month? Once per year? The less often you need to break the illusion, the better the abstraction.
When he writes about the layers of indirection, he mentions
Think of a thin wrapper over a function, one that adds no behavior but adds an extra layer to navigate.
One good use case for these wrappers is giving functionality a name. If such wrapper is well named, it can add a lot to the readability and documentation of the code. This could be combining multiple boolean operations into a well-named variable or refactoring domain logic behind a function that makes it easier to follow the main flow.
A challenge with abstractions is their asymmetrical cost as Fernando writes:
The author of an abstraction enjoys its benefits immediately—it makes their code look cleaner, easier to write, more elegant, or perhaps more flexible. But the cost of maintaining that abstraction often falls on others: future developers, maintainers, and performance engineers who have to work with the code.