We all hate technical debt. Messy software architecture, software anti-patterns, missing or failings tests, no code reviews and not enough time to fix it. Technical debt is a problem for the development team because it lowers morale, hinders work and is a problem for the business as well, as it takes time, effort and money to get rid of.
When we talk about technical debt and how to minimize it, we often talk about improving our technical skills. We talk about design patterns that work, architecture that doesn't, and we often end arguing about how modern software development should be done.
I find that focusing on the technical side of technical debt works in less experienced teams, but it falls short when working with more experienced developers. Their technical debt is often the result of the changes introduced by the business and not by their shoddy workmanship.
The technical "technical" debt
Due to their inexperience and lack of familiarity with implementing complex systems, inexperienced developers will have more technical "technical" debt. Their codebase will be messy, filled with anti-patterns, half-baked solutions and they produce unmaintainable software.
This is where learning about design patterns, having good software practices, implementing a test driven development, and having a code review process, will over time reduce most of the technical issues of technical debt.
There's a point where the technical skills of the development team are sufficient enough to introduce minimal technical issues. You'll never get rid of all the technical "technical" debt, because sooner or later you'll hit the sweetspot of price-performance, where spending time on cleaning the codebase won't provide you (or the business) with any more value. You'll be dealing more with a minor speed bump than an actual "let's rewrite the whole damn thing" detour.
The business technical debt
Even experienced teams have problems when technical debt gets introduced by the business, because the underlying assumption that their software is built on changes. This is technical debt that gets introduced without a single change to the codebase.
Imagine a business that offers dog walking services. The software is catered to fulfilling those requirements and because it's worked on by an experienced team it has minimum technical debt. They have tests, a working code review process and the development pipeline is smooth and without any major problems. A joy to work on.
But then the business decides to also offer house sitting services. Without changing a single line of code, the business and it's new requirement introduced technical debt to the system. It's a simple requirement to have, but the underlying assumptions have changed. We're not working with dogs anymore, we're working with houses as well.
The existing software is built around the assumption of dogs and dog walkers. If you change that assumption, the software starts becoming inappropriate. It still works, it's still tested, it's still documented, but it is not quite right. It was built for dog walking and not house sitting. It needs to change, if it wants to fulfill the business requirement with minimum technical debt.
Even with proper abstraction, there's still going to be things that need to be changed. A proper abstraction will just reduce the amount of changes needed, while working tests will only help with the confidence of the developer making the changes. The codebase will have to adapt to the new assumption.
Having a clean codebase and an experienced team will mean that the necessary changes will be fast, tested and reliable, but the team will still work against technical debt that was added without a single push to the system. Some of the existing code will need to be changed to fit the new assumptions, while new features will have to take into account code that was built on a old assumption.
In experienced teams technical debt gets introduced by feature requests.
How to minimize the business technical debt
A prerequisite is having a good technical skills. That way you won't have a lot of technical "technical" debt that you'll need to get rid off on top of your business technical debt. Having a good and maintainable codebase will help you, as you'll be able to quickly and with confidence introduce the necessary changes to your codebase.
The next step is getting involved with the mid-term and long term business plans. Anticipating future changes can help you to introduce an architecture that won't be fully suited for the future changes, but it will be a nudge in the right direction. If the business change never comes, it won't be such a loss (because you didn't actually implement a solution that supports that future change), but if the change comes, hopefully it will remove some of the work needed. You just need to be careful not to overdo it, because it's easier to plan something that it is to execute it. This is especially true with mid and long term business plans.
Getting rid of technical debt is an neverending story. Even if you're taking care of all the technical things, there's still the business side that introduces changes and technical debt with every new feature request.