I've seen this too. And I've also seen the converse--complexity that was added because engineers refused anything but the most myopic designs, using thought-terminating cliches like "YAGNI" or "that's hypothetical".
"Never think ahead" is obviously not good advice. There's no silver bullet here--we have to think about how likely future scenarios are, and plan for them based on the business context and needs. Many of them are unlikely or too costly to do anything about...and many of them aren't.
But then you'd have to have domain knowledge and why would you learn domain knowledge when the ideal career is a new job in a new industry every few months.
</HN>
A real world case I recently ran into where thankfully we realized we would need it and added it is versioning. A struct you pass to or receive from an API that has a version in it means the difference between being able to make changes to the internals without changing the externals and not being able to. We didn't need the version field until the second version was released. Had we just said, "Oh, we aren't going to need it," on the first version we would have been boned.
A common pattern is writing code as a series of isolated cases, when taking some time to design the general case would greatly reduce the amount of code. You add a bool parameter to a function to modify one small bit of what it does, then another one, and you add some new return value, and before long, you've got a class with several getters and instance variables represented as code in a single function, with parameters controlling which actual method is run.
What you describe a development style where the first iteration is well-designed but subsequent modifications are applied as a series of hacks and kludges. This way any code will turn into a big ball of mud over time. This is a problem.
But trying to anticipate everything in the first iteration is not the solution. The solution is to write maintainable code and apply each change with the same discipline and thought which was used when in the initial iteration. Follow the boy scout rule: After any change, the code should be in a better state than before.
I think if you misinterpret YAGNI as “you don’t need to change this later”. So your becomes rigidly hard coded, instead of having easily configurable variables and arguments. The over-engineered solution (the real YAGNI) was an interface, an object, methods and fields, only serving a very niche purpose with a lot of boilerplate.
That is not adding complexity, since the end result (the added method) is the same. You are just postponing some work until it is needed, which is prudent anyway due to opportunity cost.
"Never think ahead" is obviously not good advice. There's no silver bullet here--we have to think about how likely future scenarios are, and plan for them based on the business context and needs. Many of them are unlikely or too costly to do anything about...and many of them aren't.