There also existed many interfaces with exactly one implementation, which just increases the cognitive load when writing code because you see an 'IDoX' interface only to discover it is ALWAYS an 'XAdapater' that if you had known, would have saved mountains of logic (ie, getting a new 'XAdapater' from the container while copying some stuff from the 'IDoX' interface). It was a mess.
Are they going to be glad that all these abstractions exist at some point? Probably not. This is a pretty simple, but mature project. There's no real reason to refactor it at this point. The business owners just wanted to know what was going on and why simple changes took months to implement.
It still can make sense if you've got a very complex bundle of isolated stateless business logic but in practice I find that most dependency inversion I see these days isn't for that. It increases the SLOC by 30% and reduces code coherence just so that a test can run in 20 milliseconds instead of 2.1 seconds.
In many cases those unit tests check to see if you put a number in one end of a class that the same number comes out the other end. What bug is that going to catch?
Meanwhile all of the actual bugs are probably hiding in the database queries or the interaction layers created as a sacrifice to bob the bearded god of unit testing.
The most fascinating instantiation of this idea is, I think, the notion that because unit tests are horrible to work with that unit tests can therefore "drive" design because it'll make you fight to make the unit test less horrible. It's like advocating an abusive relationship to help fix your life because the abuser won't be afraid to point out all of your flaws which you can then fix. The two corollaries that never seems to permeate are A) maybe when unit tests become horrible to work it's means they were also the bad code that coupled too tightly? and B) maybe it's cheaper to spot bad design by learning what to look for rather than building a unit test that will punch you in the face for it?
I once had a bug scenario I wanted to reproduce with a test where the time was grabbed from datetime.now() in the first half of the scenario and indirectly via a postgres query in the second half (with a time delimited query).
With libfaketime I injected a new running service time and postgres service via the test runner. It let me do TDD to create a reproducible test - what you thought was impossible.
I could have jumped straight to dependency injection on that mess of a code base but that would have meant a risky architectural change that I would have to have done without tests. That is bad practice.
This sort of thing was not really possible in 2001 but it's possible now.
There are people who cargo cult and people who do things for a reason. It may not always be obvious which category a developer was in just by talking to them or looking at their work.
Imagine the following scenario: you have two projects each having the same functionality and the same level of test coverage. Project A is 30% longer than project B but project A's test suite runs in 1 second while project B's runs in 2 minutes. Personally, I am taking project A every time (but I'm also probably trying to figure out how to get that 30% down to 0.1-2%).
The path to project B is paved with 50 decisions to do the thing that took 2.1 seconds rather than 20ms.
Project A's tests will usually be sacrificing realism in favor of speed. The practical upshot of that is entire classes of bug won't get caught.
To take some recent practical examples off the top of my head: I have some tests that do 404 checks on all web pages. I have another that checks if sent emails are outlook compliant. These checks necessitate that the tests be slower but somebody still needs to do this.
I know there are plenty of people out there who think like you do. That was kind of my point. Realism in testing is an underappreciated quality.
I decided to follow the «Wrong abstraction is worse than duplication» approach. Doing this for 5+ years now and did never regret it.
Sure, copy-pasta sucks but in comparison to over engineering it’s an acceptable price to pay.