The term “code debt” in computer programming refers to the idea that certain decisions in writing computer code will lead to future consequences which have to be dealt with. By saving time now, the programmer is setting himself up for the need to take more time later. In other words, he is “borrowing” from the future. While this metaphor is helpful to use in many situations, there are some significant differences between code debt and real debt, and those differences need to be included more often in software development discussions.
First, for those who are not familiar with the concept of code debt, let me give a simplified example. Let’s say that I need to deliver a project next week, but I have more than a week’s worth of tasks to do. One of those tasks is logging. Now, it is problematic to deliver an application with insufficient logging, but let’s say that I determine that the delivery deadline is more important than the logging system.
Now, the logging system is a necessary feature that has to be implemented. Without it, it would be impossible to trace bugs, customer problems, or security threats adequately. However, I can hold off building the logging system until after I release the current code. So, I make the decision to ship in a week, and deliver the logging system the next week. Essentially, what I did was “go into debt” with the code. The delivery did not have everything it should have had, but I chose to deliver the rest later. Just because lacking the code didn’t block the release didn’t mean that the change didn’t need to occur, I just chose to put it off until later. What the term “code debt” helps us with is remembering (and communicating to management) the fact that certain decisions mean that we are saving time now in order to spend time later. That way, managers can better understand why you are still working on the system despite the fact that it already “shipped,” or why you are working on features that are already delivered. “Code debt” translates complicated programmer intuitions and decisions into a language that is better understood by managers.
Code debt is usually subtler than this example. Code debt more often comes around because some part of a module needs to be rewritten to more cleanly support new features. Let’s say it takes two days to implement a new feature in the module, but it would only take one day to implement that feature (and maybe future features like it) if we rewrote the module differently. However, the rewrite will take five days. For any one particular feature, the two days is faster, but, for the long haul, the rewrite is probably faster. Choosing not to rewrite the module introduces code debt in two ways, because not only are you not rewriting the module, you are also adding to the amount of code that needs to be rewritten, as your new feature will also need to be rewritten when you rewrite the module.
That said, I want to mention a few ways that code debt is not at all like real debt. I think that the usage of the phrase causes people to overly associate the mechanics of monetary debt to what is actually happening in code debt, and that can lead to strategic technology mistakes.
The first difference is that all real debts have to be paid, but some code debts don’t. If I borrow money from the bank for a project, and that project fails, I still owe the bank the money. On the other hand, if I accumulate code debt for a project that fails, I don’t owe the code debt at all. That is, if the software winds up going unused, then any amount of “payment” I made on the code debt was actually wasted. This is why, in newer projects which are not guaranteed success, accumulating code debt is actually beneficial, because this is work that only has to be done on success. If the project as a whole winds up failing, or the company goes a different direction, then the code debt is cancelled and becomes money saved.
The second difference is that sometimes code debt has early payment penalties. Programmers like to refactor their code so that it works well with the features it is supporting. Optimally, the code is structured such that future features are easy to implement and work with the architecture, not against it. However, nobody actually knows what the future requirements will be. I’ve had projects where fundamental assumptions were uprooted within weeks of launching. If you refactor too early you can risk having to refactor again shortly because assumptions change. While you can’t predict the future, it is good to keep in mind that waiting to refactor can have benefits.
The final difference is that sometimes code debt gets smaller as it accumulates. The more you add to the system, the more you really understand what you are doing. The more you push on a system’s boundaries, the clearer it is how the system should work. The more you integrate other systems into your system the better you understand where the ideal interface points are. All of this will make code debt cleanup easier and faster, because you will be more certain of where it should occur and what the final result should look like.
The key to managing code debt is not only keeping track of it, but being wise about when to pay it off. If you have no code debt, or you always try to eliminate your code debt, you could be making strategic errors in utilizing developer resources in the most productive ways.