Many engineers have experienced the slog of working in a bloated codebase that seems to break at the drop of a hat. Asking for time to fix ingrained complexity is a tough sell for product managers: engineers want time to change the code, but not add any features nor fix any bugs.
It is understandably difficult to see how refactoring can be a good financial investment, but with the right strategy, we can earn a significant return.
First, let’s look closely at technical debt and technical interest.
Technical debt builds up when engineers take shortcuts to get features out faster. It is the difference between optimal and realistic release dates.
Tech debt in a codebase is not an immediate indication that software quality is suffering. Rather, tech debt is perfectly normal and sometimes an indication of healthy growth.
Let’s draw an analogy to financial debt. If a business owner can sell a product faster than she can make it, she might take out a loan to expand operations to turn a greater profit. In this case, the debt indicates the company is growing very fast — a sign of financial health.
Similarly, an engineer might take out “tech loans” to build a system out more quickly so it can start making money faster.
The difference is financial debt must be repaid or the bank will come knocking on the door. Technical debt can accumulate for years, silently taking its interest payment in the form of reduced productivity, defined here as technical interest.
The fact that technical debt carries interest is often overlooked. Every time a technical loan is taken, future changes are just slightly more difficult, and we slightly increase the chance of introducing bugs down the line.
Over time, this added difficulty becomes noticeable by engineering as a general feeling that things are getting harder to change. Inevitably project velocity will drop due to increased complexity.
Some types of tech debt have higher interest rates than others. For example, a snippet of code that might need some better inline documentation but likely won’t be touched by anyone for a few years is low interest. Conversely, a set of core files that are interdependent and excessively complex have a much higher chance of having a negative impact, so they are considered high interest.
Eventually, tech loans need to be paid down or the software can risk “technical insolvency” — the point at which 100% of the engineering effort is used to maintain the project and fix bugs. Technical insolvency usually leads to a company labeling some code as “legacy” and rebuilding its product from scratch.
To pay down technical debt, we use the process of refactoring.
Martin Fowler, an authority on the subject, describes refactoring as:
“… a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior.”
This statement is true, but to refactor with purpose we must go beyond this definition and understand why we want to refactor in the first place.
By applying refactoring to a complex system, we end up with software that is functionally equivalent to what it was before. However, time to develop new features, the chance of introducing bugs, and the learning curve for new engineers are all significantly reduced.
By refactoring, we lower our overall tech debt interest rate and gain a return on our investment of the time it takes to refactor.
As with any project, before beginning a refactoring effort we need to answer these two questions:
- Where do we start?
- How do we know when we’re done?
Where do we start?
Let’s again compare with financial debt. Let’s pretend we have taken the following loans:
$10,000 @ 20% APR
$1,000 @ 5% APR
If you have $1,000 to spend on paying down loans, where do you spend it? It should be obvious that even though we could afford to pay off the smaller loan first, the money should go to the larger loan because it has a higher interest rate. The smaller loan is an “easy win”, it makes us feel good now but it is not the best long-term decision.
In this case, the answer to the question “where do we start” is to start with the loan that has the highest interest rate, because that will have the most downstream impact.
The same is true of software. When engineers get time to pay down tech debt, they often gravitate toward low-interest changes, the easy wins. The trouble is, there will always be more easy things to clean up and the highest interest rate debt will go unchecked.
How do we know when we’re done?
The simplest answer is “when the debt is gone”, but that’s not realistic. Both financially and in software development, the answer is “when the debt is no longer a burden”.
We should strive to keep our tech debt interest rate at a comfortable level, that is, at a point that it doesn’t impact our ability to be agile developers.
I know that sometimes on Friday afternoon we’re just looking to fill out our day and want a quick cleanup task to work on. There’s nothing wrong with that.
When it comes to concentrated refactoring efforts aimed at making the code easier to read, modify, and test — ignore the easy wins and go for the hard stuff. In the long run, you’ll have a greater impact on everyone.