As of recent, I’ve been coming across abstractions that hinder me more than help. I’m writing this blog post to vent out some of my frustrations, as well as to properly identify what makes an abstraction a hindrance rather than have it relieve cognitive load.
Most of the leaky abstractions that I have been coming across as of late have been hiding things they shouldn’t be. One of these things is critical behavior switching. Abstractions that switch their critical behavior through unclear mechanisms will most definitely leak. All critical behavior switching should be handled higher in the stack, so anyone that comes by doesn’t need to dive to deep.
The more complex the abstraction, the more it leaks. This is unavoidable. Actually, it is avoidable. Just split up into smaller abstractions. This will be abundantly more clear than having one abstraction handle every case. If the behavior of the abstraction cannot be communciated through the abstraction’s name; it probably needs to be split up. The utility of an abstraction is not in how many cases it can be manipulated to handle. The utility comes from decreasing the amount of entities someone needs to keep in their head to understand the behavior of the code path.
I’ll repeat this. If the behavior cannot be communicated through the abstraction’s name, then it is unclear.
The misplaced importance of the DRY rule (do not repeat yourself) is often the cause these leaky abstractions. The perceived value of being more concise comes at a much greater cost of losing detail. I’ve been observing this blunder in test suites. Personally, I use test suites as documentation on the component’s behavior. If the test suites’s assertions are hidden under layers of abstraction, the utility of test suites as documentation is completely lost.
Even though I mostly have been working on components with minimal state which are pretty much data pipes, I have been coming across unecessary abstractions. I can only imagine how bad it must be in a poorly written object oriented code base.