As I've gone in and out of codebases enough times (must be in the thousands now surely!) I've started to see a ton of different ways people architect code. Here's a newsflash to those of you inexperienced yet; there's rarely such a thing as perfection in the real world. The real world is almost always convoluted, full of a mishmash of ideas and forgotten architectural decisions.
You see in the real world you have technical debt that just slips through the cracks. It's nobody's fault, but it's also everybody's fault at the same time. Most companies that I've worked with naturally have the vast majority of time spent on features and very little (if any) spent on technical debt.
The reasoning I've heard is quite simple; clients want features and don't expect bugs to exist in the first place, thus bugs aren't of any importance to them. And as the purpose of for-profits is to make money, they also align importance to features and very little of it to technical debt, to align with the client.
I feel like I've been around for enough time now to sort-of formulate an opinion on this and maybe have some solutions as well. Those solutions I try to apply in my day-to-day and I got to say, it has had an immense result. I'm much, much less of a grumpy developer, I don't doubt or second-guess everything and am in general much, much happier.
It's the fault of the management
Naturally as in any argument, there's more than one side to the story. It's easy to look at things from the developer's perspective and say that if only the management would increase the time spent on technical debt all of our problems would be solved. From there on out we would have plenty of time to fix bugs and refactor all of the mess we otherwise would just leave behind!
After all the mess was made there in the first place by constant indecisiveness of the management which meant that features built with the initial feature-set in mind no longer work well with the new feature-set, requiring workarounds and ugly duct-tape methods to make it all not collapse in itself.
Also if we'd only know the roadmap a bit more ahead than just mere 2 weeks at a time (be damned, scrum!) maybe we could make better decisions when it comes to architecting our code-base and features.
It's the fault of the engineers
But if you'd for a moment put yourself in the management shoes, you'd realize you are part of the problem, too! The management would say that they put a bunch of you in a room to "groom" tasks hoping that the outcome would be accurate estimations from experts. Estimations which they can then confidently tell their clients or make quarter plans with, meticulously planning out a year to build an ever-lasting source of profit so that everyone would get fed.
But what comes out instead? You put a bunch of "experts" in a room and they can't even often agree on an estimation! So then you do "re-groomings", after sending those "experts" back to study the area of code they need to touch for them to get on the same page, which all of them failed to do beforehand, fully knowing there was grooming scheduled and what the topics for said grooming were. And then when the sprint nears a close we realize those estimations from "experts" weren't accurate to begin with.
And often because those estimations aren't accurate those same engineers then are left to engineer themselves out of the corner they put themselves in to save face, which results in rushed code and ... guess what? If you guessed technical debt, you won the jackpot.
So then what does one do?
It's a real pickle I must say, with no clear-cut answers. But what seems recurring to me from being in the middle of this day-in, day-out, is that like our neanderthal-cousins of old, we also clearly lack imagination - or better put, understanding where the problem comes from as to then either solve it or learn how to avoid it.
As managers, you already know the pattern that your engineers create - the under-estimations, the over-estimation - so you then sort-of force your hand for the team to choose one of those, and you can then work accordingly. If your engineers under-estimate, you add a little extra time yourself so that your plans are on time. Or if your engineers over-estimate, then you know you can probably squeeze a few more work items to the sprint now and again. Most managers prefer over-estimations.
As engineers that are stuck in the scrum world, you already know you can't see the whole picture. The whole picture isn't even always known. Not to anyone. Not even to managers or product owners. But you can take it upon yourself, along with your team members, to always try to architect things while thinking forward - by making things abstract enough to be easily expanded, or at the very least modular enough to be easily extended.
Care, just a little bit more
Whichever approach you tend to take, lazily throwing stuff together rarely tends to work. Swimming against the stream rarely tends to work as well. But it's funny to me that as engineers, as people whose literal job is to foresee potential failures in things, a lot of us completely fail to foresee the management's direction and tend to swim against it. We even get bitter when management comes to us with a feature to implement that requires a refactoring, because we failed at our job.
We're communicators, us engineers. Our job is to take in human language and translate it into machine language. As with most translations, a literal translation rarely tends to work well. And just like a translator needs to add some spice to fill that gap, you need to read between the lines to understand the bigger picture and to architect your code in a way that doesn't come back to bite everyone in the ass. Don't just take a task and do it, take it and think about it. Think about it a lot. Think most of the time, then write.