Abstractions are everywhere
Basically everything in world as we know is composed of abstractions that help us achieving our goal without thinking of all little pieces that have to come together, interact with each other to intentionally cause reaction. Like when you are wearing a pair of shoes, they are composed of leather, cloth, fabric. There is glued rubbery sole at the bottom. All this pieces combined will create an abstraction that will take away the pain.
Building software is no different. There are abstractions meant for software developers. Very common ones are REST API’s. They hide all the complexity of creating objects and entities behind well specified normalized set of HTTP requests. Every time you create a class you are trying to hide a complexity, every time you are creating interface you are creating a way how to switch between different implementation of complexity. Every time you are creating modules for your classes to group them in some logic way you are creating another level of abstraction.
We are creating abstractions for the users as well. Basically UI layer is abstraction for users that will interact with application in simplified way. When user will press the button to generate report and send it as mail, he do not have to be aware of how report is generated, how are data pulled out from database, how is data parsed into template, how is email sent to recipient, how are packets transmitted over the network etc.…
In my opinion there are generally two types of abstraction, good and bad ones.
If I would have to come with a definition, good abstraction is when the user does not have to be aware of it, so there is no need to go one level down in the chain. It just do their stuff exactly as expected, it just works. Programmers like me that spent most of the time dealing with high level languages, they are working on the top of so many abstractions that they do not even realize it.
High level languages enable developers to do their stuff while not bothering them with underlying complexity like deallocating memory in managed code, because garbage collector (GC) will take a care of that. Instead of dealing with registers, memory addresses and call stacks high level language is dealing with variables, arrays, objects, functions etc…It provide a way how to detach from the hardware and be more productive. There are a lot of layers of abstraction between written code and actual instructions that is CPU able to execute. For example code in some .NET language like C#, is first compiled to intermediate language that is executed on virtual machine called common language runtime (CLR) and then compiled second time via just in time (JIT) compilation.
They are exactly opposite. They are either not sufficient or not working properly. Like when you need to go debug third party library because it do not release memory properly or when you need extend API to support basic functionality like returning id of entity that was created.
Have a problem?
There is fundamental theorem of software engineering that is saying “We can solve any problem by introducing an extra level of indirection.” And this is kind of true. When we take a look at design patterns from GOF, basically all of them provide normalized solution to already solved problems by creating another layer of abstraction for hiding a complexity or they are introducing a way how to elegant switch between different implementation of complexity.
Also some design methodic like SOLID explicitly encourages creating abstractions. That is certainly good in general, but sometimes there are cases where I ask myself if it is even worth it. There is a lot of to over-engineering a project, like I do not need right now to create super abstract factory 9000 to just create User object. I am much more of person that is creating abstraction only if I feel the pain by not doing so.
Sure there are guidelines that explaining good software should depend upon abstraction not concretism. But I still think we need to do compromises between what is good abstraction and what is redundant ballast, and that depends only on developer to do such decision.
So in the end I think it just depends, like every decision in software architecture is based on tradeoffs and compromises.