> The point of abstraction is to limit blast radius of requirement changes.
The problem is in many/most? systems there's no way it can possibly do this, because the abstraction that looked like a perfect fit for requirements set 1 can't know what the requirements in set 2 look like. So in my experience what ends up happening with the abstraction thing is people put all sorts of abstractions all over the place that seem like a good idea and when requirements set #2, #3, etc come along you end up having to change all the actual code to meet the requirements and all of the abstraction layers which no longer fit.
To choose a couple of many examples from my personal experience:
- One place I worked had a system the author thought was very elegant which used virtual functions to do everything. "When we need to extend it we can just add a new set of classes which implement this interface and it will Just Work". Except when the new requirements came in we now needed to dispatch based on the type of two things, not just one. Although you can do this type of thing in lisp and haskell you can't in C++ which is what we were using. So the whole abstraction ediface cost us extra to build in the first place, performance while in use and extra to tear down and rewrite when the actual requirements changed
- One place I worked allowed people to extend the system by implementing a particular java interface to make plugins. Client went nuts developing 300+ of these. When the requirements changed it was clear we needed to change this interface in a way a straight automated refactor just couldn't achieve. Cue me having to rewrite 300+ plugins from InterfaceWhichIsDefinitelyNeverGoingToChangeA format to InterfaceWhichIsHonestlyISwearThisTimeAbsolutelyNeverGoingToChangeB format. I was really happy with all the time this abstraction was saving me while doing so.
Most of the time abstraction doesn't save you time. It may save you cognitive overload by making certain parts of the system simpler to reason about, and that can be a valid reason to do it, but multiple layers is almost never worth it and the idea that you can somehow see the future and know the right abstraction to prevent future pain is delusional unless the problem space is really really well known and understood, which is almost never the case in my experience.
The problem is in many/most? systems there's no way it can possibly do this, because the abstraction that looked like a perfect fit for requirements set 1 can't know what the requirements in set 2 look like. So in my experience what ends up happening with the abstraction thing is people put all sorts of abstractions all over the place that seem like a good idea and when requirements set #2, #3, etc come along you end up having to change all the actual code to meet the requirements and all of the abstraction layers which no longer fit.
To choose a couple of many examples from my personal experience:
- One place I worked had a system the author thought was very elegant which used virtual functions to do everything. "When we need to extend it we can just add a new set of classes which implement this interface and it will Just Work". Except when the new requirements came in we now needed to dispatch based on the type of two things, not just one. Although you can do this type of thing in lisp and haskell you can't in C++ which is what we were using. So the whole abstraction ediface cost us extra to build in the first place, performance while in use and extra to tear down and rewrite when the actual requirements changed
- One place I worked allowed people to extend the system by implementing a particular java interface to make plugins. Client went nuts developing 300+ of these. When the requirements changed it was clear we needed to change this interface in a way a straight automated refactor just couldn't achieve. Cue me having to rewrite 300+ plugins from InterfaceWhichIsDefinitelyNeverGoingToChangeA format to InterfaceWhichIsHonestlyISwearThisTimeAbsolutelyNeverGoingToChangeB format. I was really happy with all the time this abstraction was saving me while doing so.
Most of the time abstraction doesn't save you time. It may save you cognitive overload by making certain parts of the system simpler to reason about, and that can be a valid reason to do it, but multiple layers is almost never worth it and the idea that you can somehow see the future and know the right abstraction to prevent future pain is delusional unless the problem space is really really well known and understood, which is almost never the case in my experience.