This matches my experience as well.
Someone here commented once that abstractions should be emergent, not speculative, and I loved that line so much I use it with my team all the time now when I see the craziness starting.
It's advice I like, as I'm prone to falling into design paralysis while trying to think of the One True Abstract Entity.
I more think the point is that the abstractions now obvious to you emerged at some earlier point that you now recognize, and application makes sense. Speculative implies predicting, and if you've been around long enough in the same domain, it's likely you aren't guessing.
It's probably something that applies more to younger devs than older, but not exclusively of course.
Dang, I love that line, too. It is 100% correct, and the opposite of what OOP teaches.
I think every dev lives in fear of the situation where abstraction hasn't been done when it really should have. Maybe some junior dev came in after you and shoehorned in a bunch of features to your "simple" solution. Now you have to live with that but, guess what, there's no time to refactor it, in fact the business just wants even more features.
As usual these rules work best if everyone on the team understands them in the same way. If you work with people who can only see what is right in front of them (ie. the current feature), then it'll never work. You can always fit "one more feature" into the perfect codebase without thinking about the abstractions.
Usually because intent is far clearer in the former case.
I truly believe this comes from devs who want to feel smart by "architecting" solutions to future problems before those problems have become well defined.
So a little bit is always good, and more is sometimes very good -- even memorably good. Together these cause many of us to extrapolate too far, but for understandable reasons.
Compare and contrast https://people.mpi-sws.org/~dreyer/tor/papers/wadler.pdf
I'm sure it's super flexible but the exactly same thing could have been achieved with 8 YAML files and 60% of the content between them would be identical.
I found that over time my senses have been honed to more quickly identify things that are important to deeply study and plan right now and areas where I can skimp more and fix it later if problems develop. I don't know if there was a short cut to honing those senses that didn't involve a lot of pain as I needed to pick apart and rework oversights.
Changing the function signature or the type then generated cascade of compiler errors that tells you exactly what you touched.
Weak non specific types does not have that property and even with tests you cannot be sure about the change and cannot even be sure you are upholding invariants
If you've been monitoring properly, you buy yourself time before it becomes a problem as such, but in my experience most developers who don't anticipate load scaling also don't monitor properly.
I've seen a "senior software engineer with 20 years of industry experience" put code into production that ended up needing 30 minute timeouts for a HTTP response only 2 years after initial deployment. That is not a typo, 30 minutes. I had to take over and rewrite their "simple" code to stop the VP-level escalations our org received because of this engineering philosophy.
There is nothing to suggest you should wait to optimize under pressure, only that you should optimize only after you have measured. Benchmark tests are still best written during the development cycle, not while running hot in production.
Starting with the naive solution helps quickly ensure that your API is sensible and that your testing/benchmarking is in good shape before you start poking at the hard bits where you are much more likely to screw things up, all while offering a baseline score to prove that your optimizations are actually necessary and an improvement.
This is something I tend to consider far, far worse than "AI Slop" in practice. I always hated Microsoft Enterprise Library's Data Access Application Block (DAAB) in practice. I've literally only ever seen one product that supported multiple database backends that necessitated that level of abstraction... but I've seen that library well over a dozen times in practice. Just as a specific example.
IMO, abstractions should generally serve to make the rest of the codebase reasonable more often than not... abstractions that hide complexity are useful... abstractions that add complexity much less so.