A good abstraction can be powerful when carefully implemented and not doing so might result into indirections which can hurt your code’s popularity, read on to understand how.
When building software, we all have a toolset consisting of libraries and frameworks, which are part of our workflow.
Specifically, they are part of our workflow for different reasons, such as efficiency, robustness, and so forth. However, all of them have one thing in common: they are good abstractions over something otherwise complex.
This is why many understand just how powerful abstractions can be and why it makes sense to provide abstractions within our own work. However, not being careful about the conditions for the abstraction can quickly make your code much less likeable and unpopular.
Example of a good abstraction
Let’s have a look at an example of a good abstraction: ASP .Net Core middleware.
Handling HTTP requests/responses actually involves a lot of complexity. It makes sense to encapsulate that complexity and offer a simple abstraction that allows developers to build modular, reusable components that can be easily composed into a pipeline.
Moreover you most likely never had a reason to understand, or change, its source code in order to adapt the abstraction into your flow. These are the qualities of a good abstraction and what makes abstraction so powerful when done right!
Example of code indirection(s)
Imagine a simple CRUD service responsible for creating a new customer record. You are assigned a task to add a small thing to its behavior.
You know the service does not contain any domain objects or business logic. Cloning the repository, you quickly find the WebAPI project, and there is only one controller which looks like this:
“Ok, so they are using a community validated library, Mediatr, that’s good!” you think to yourself and push on to locate the request handler.
Essentially, you manage to find it in one of the referenced projects, but only to find yet another layer of indirection:
“Ok, right, all I have to do now is find the applied implementation of ICustomerService to get some insight into how the code works”, you tell yourself and spend another handful of time digging to find the implementation you are looking for:
Think about it. For three lines of code, that are actually doing something, you just had to navigate through an equal amount of layers of indirection. You probably also had to manoeuvre many lines of configuration for DI as well.
By the time you find what you’re looking for, you’ve already spent a considerable amount of energy to achieve something simple. There is a good chance this codebase won’t make it into your top 10 favorites.
These are bad abstractions and are really just indirections. Indirections are detours which make code harder to navigate effectively. Simply because the idea of abstractions has been applied to an anemic codebase.
The consequences of having layers of indirection
Consumers of your code are paying the highest price trying to work with your code to adapt it. If you are creating code for others to consume, too many layers of indirection can make your code undesirable to work with.
Make sure you actually have complexity to hide, as opposed to having anemic code. Also, make sure you hide it properly so it is composable and extendable. Last, properly test your abstraction to reduce amount of leakages.