When working within a legacy system, there can be quite a bit of pre-existing technical debt. As feature requests come rolling in with an ever-growing pressure to deliver quickly, the technical debt only grows. So how do you stop the bleeding? Where do you start?
As Software Engineers, we notice different issues than the business and customers. We see the spaghetti code; we sniff out the code smells; we look at the 16,000 line files with a sense of dread and terror. The customers don't see these things and will only notice the side effects that negatively impact their experience. Likewise, the business is concerned with pleasing the customers and providing them value.
It's easy for us to rattle off the downsides of accumulating technical debt: how it is not maintainable and how it can lead to high developer turnover; however, the business likely is not going to listen to these cries. They want new features, that's what sells software, and that's what keeps the lights on. So instead of pointing fingers and expressing our anguish, let's shift the priorities.
While we may want to start dealing with the most atrocious examples of technical debt in our systems, that doesn't always align with what's most important to the business and our customers, so the first thing we need to do is figure out what is important to them. What are the critical features, the ones that result in customers leaving if they don't work, the ones that mean the difference between signing a new client and losing them to a competitor? Once we know what these critical features are we have our set of priorities.
Now that we have an idea of the business's priorities how do we prioritize from our perspective? We can take a look at the business-identified critical features and determine which ones require the most improvement: which features have the most reported issues or most frequently reported issues?; which take us the longest to debug?; give us the most grief?; lead to the most burnout? Answering these questions will help us order our priorities.
At this point, we've asked a lot of questions to both the business and ourselves. We should have a list of features that the business deems most important to our customers, and each of these features should be ranked by the amount of improvement needed. What process you employ to obtain and rank these priorities is up to you, but once you have completed this exercise, you're almost ready to start taking action.
Before that can begin, it's a good idea to prepare a backlog of tasks. These tasks should be small in scope and should drive you towards the improved code base you desire. Some of these tasks will need to be finished in a particular order, so be sure to keep track of that when you create the tasks in whatever system you are using to manage your projects (Jira, GitHub, etc). It may be helpful to refer to these tasks as "Technical Debt Tasks" or something along those lines.
When planning your sprints, try to include at least one technical debt task (preferably one per each developer on the team), fitting the tasks in with the feature work and bug fixes that are chosen for the sprint. This will help ensure that you are always making progress towards your goal of reducing technical debt in your system.
There are alternatives to this approach. Some teams choose to have special coding sessions to work on technical debt. Doing this can indicate that your team doesn't value the reduction of technical debt as high as it values building features and eliminating bugs. Also, these sessions are sometimes scheduled after normal working hours - requiring developers to put in extra time to participate, giving off the impression that the accrual of technical debt is the fault of the developers. It most certainly is not. Technical debt can grow as a result of any participant in the software development process or could even be the result of a flaw in the process being used.
Another alternative to working technical debt tasks into your sprints is to not address it at all. Instead, some teams may choose to replace existing services with new ones. While this may seem tempting, especially when the code is exceptionally messy, it requires developers to split their time between building new services and maintaining the old ones. If this works out for your team, by all means, do what works for you, but it can be a major time waster if it is not executed properly.
Choosing to work on technical debt tasks as part of each sprint continuously adds value at the end of each iteration. You avoid putting it off until you have time for a special refactor session that may never happen, and you won't find yourself waiting for the big payoff that your new services will deliver (assuming you ever finish them). Take it one sprint at a time and your code will keep getting better and better, baby steps.
All of us manage our software projects differently: maybe you use sprints, maybe you don't. If your team doesn't use sprints, the advice above still applies. The point is to work on technical debt tasks as part of each cycle. Treat them as if they are just as important as feature work and bug fixes because they are.
Addressing technical debt is an ongoing effort so these phases of identifying debt, preparing tasks, and working them into your sprints will be repeated over and over again. It's an iterative process. When you first set out on your mission to reduce technical debt ask a lot of questions. Ask the business, ask your customers, ask yourselves. The more insight you have into what parts of your software matter the most, the better off you will be. Once you have collected your data, start prepping your tasks, and work them into each sprint. Lather, rinse, repeat, and your system will start becoming more maintainable and reliable.