IEEE Software - The Pragmatic Designer: Ur-Technical Debt

This column was published in IEEE Software, The Pragmatic Designer column, July-August 2020, Vol 37, number 4.

ABSTRACT: The term technical debt was coined by Ward Cunningham in 1992. In recent years, people have broadened the definition to include ideas not present in the original formulation, including lack of skill, expedient hacking, and obliviousness to software architecture. By lumping these together, it’s harder to choose the right repair. This article proposes that we use the term ur-technical debt to refer to the original idea, which was: When I build systems iteratively, my understanding of the problem and solution grow gradually, and inevitably my current thoughts do not match code I wrote earlier, so I must expend effort to fix that code, much like paying interest on a debt.


These days, everyone uses the term technical debt. It’s so prevalent that we shorten it to tech debt or even just TD. What counts as technical debt has expanded over the years, which has caused many people to lose sight of the interesting phenomena that Ward Cunningham was talking about when he coined the term in 1992. Today, any code that a developer dislikes is branded as technical debt. Tech debt is also hacky code, code written by novices, code written without consideration of software architecture (so-called “big balls of mud”), and code with anti-patterns flagged by static analysis tools.

If you have never read Ward Cunningham’s original paper [1], you might be shocked to learn that it says tech debt originates from using iterative instead of waterfall development. At the time, refactoring was not a widespread idea, so he invented the debt metaphor to explain to his manager that building iteratively gave them working code faster, much like borrowing money to start a project, but that it was essential to keep paying down the debt, otherwise the interest payments would grind the project to a halt. That idea is quite different than what we call tech debt today.

It’s easy to understand why the definition has been broadened. These other topics are worthy of study, they are technical, and the metaphor of debt applies. Our community has accepted that tech debt is something to pay attention to, so saying an issue is a kind of technical debt is a way to draw people in.

When you set out to pay down your technical debt, having a narrow definition of technical debt is helpful because different ailments require different medicine, and lumping many ailments together makes it harder to choose the right medicine. The momentum behind the current, broad definition is too strong to be turned back, so, by adding the prefix ur- that means “original”, I can refer to the narrower idea as ur-technical debt.

The original idea

What is the original idea, exactly? Simply put, ur-technical debt arises when my ideas diverge from my code. That divergence is inevitable with an iterative process. The original paper connects your software process choices (waterfall or iterative) with financial concepts. Given that you have some amount of time to build something, say a year, how can you use that time?

When you choose an iterative process, you use that time to deliver several partial solutions. You don’t understand the full requirements until the last iteration, so for most of the year, the ideas in your head are partial. The code embodies your partial understanding because it cannot be more insightful than the ideas in your head. The original paper argues that this is ok, so long as you don’t leave the code in that state forever, and introduces the debt metaphor: “Shipping first time code is like going into debt. A little debt speeds development so long as it is paid back promptly with a rewrite.” [1]

When you choose the waterfall process, you are using the time to aim carefully and hit your target a year down the road. Your ideas from the analysis phase match your code because you looked at all the requirements at once and wrote code just once. The original paper says “working out a program in detail before programming begins … amounts to preserving the concept of payment up-front and in-full.”

Why put up with divergence or rework existing code when waterfall promises code aligned with ideas? Because a waterfall process only looks good if you believe that what you build will be flawless and the requirements will not change. If either of those happen, then you need a second waterfall, which is just another name for very slow iterative development.

The original paper argued the point this way: “We believe this process leads to the most appropriate product in the shortest possible time. … The modularity offered by objects and the practice of consolidation make the alternative, incremental growth, both feasible and desirable …”

Consolidation is critical

As you tackle new requirements, your understanding changes. Ward Cunningham called this “consolidation” of ideas. New abstractions emerge, either arising from better understanding of the problem or recognizing a more suitable design as the solution. When using iterative development, this consolidation happens repeatedly across iterations. “Mature sections of the [WyCash] program have been revised or rewritten many times providing the consolidation that is key to understanding and continued incremental development.” [1]

When we use iterative development, it’s inevitable that our ideas will evolve. What’s not inevitable is that we change the code to match our ideas. Some developers choose to leave existing code alone, following a process of code accretion, or to coin another term, sedimentary development. For a while, you can apply brainpower to reason across the gap between what you understand compared to what you see in the code. But your mind has limits and the greater the divergence, the worse you perform. “[I]f you develop a program for a long period of time by only adding features and never reorganizing it to reflect your understanding of those features, then eventually that program simply does not contain any understanding and all efforts to work on it take longer and longer.” [2]

Choosing sedimentary development as a process is walking into a dead-end. I often hear lip service to refactoring and expensive restructuring of code, but I rarely find teams that earnestly embrace Ward Cunningham’s advice. Teams instead rely on developers reworking code on the side or as off-schedule work, not choosing to make refactoring part of their official process. A software project that follows iterative development but has no process to restructure code is implicitly declaring bankruptcy as its exit strategy.

Companies often advise employees not to write anything in email that they would feel uncomfortable having read back on the witness stand in a trial. That’s a vivid image that encourages clear communication that can’t easily be taken out of context and misunderstood. It would be interesting to hear companies say the same thing about code, advising us not to check in code we’d feel uncomfortable being used to teach programming. That might be exactly the vivid image we need to keep our ideas and code consolidated.

It’s Not About Hacking

Ward Cunningham is known for many ideas, not just technical debt. You might know him as a ringleader in the Agile software development movement, or for his role in introducing patterns to software development, or for inventing the first wiki. In 2011, the IEEE interviewed him for the Annals of the History of Computing and the topic of technical debt never came up [3]. However, the idea of technical debt has piqued the interest of the software community over the years, to the point where in 2009 he decided to post an explanation of what he meant by technical debt and contrast it with broader interpretations [2].

In that explanation, he says that technical debt is not bad code, code written by novices, or expedient hacks. “A lot of bloggers at least have explained the debt metaphor and confused it, I think, with the idea that you could write code poorly with the intention of doing a good job later and thinking that that was the primary source of debt.” By saying primary source, he’s leaving the door open that there is more than one source of debt, but he doesn’t open it much, as he continues with “I’m never in favor of writing code poorly, but I am in favor of writing code to reflect your current understanding of a problem even if that understanding is partial.”

If you knowingly write hacky code, or you allow inexperienced developers to use their first draft code, you undermine the very thing that makes iterative development viable. “In other words, the whole debt metaphor, let’s say, the ability to pay back debt, and make the debt metaphor work for your advantage depends upon your writing code that is clean enough to be able to refactor as you come to understand your problem.”

The fascinating, surprising thing about ur-technical debt is that it happens even under the best circumstances, say with expert developers who always choose to fix debt immediately. It’s inherent to using an iterative process and acting with a partial understanding. Expanding the definition of technical debt to less ideal circumstances yields a completely unsurprising conclusion, that novices and hacking cause trouble on a project.

It’s Not About Decision Making

These days, most everyone uses iterative development. Once I start down that path, I am committed to creating technical debt because I am looking at partial requirements and writing code with partial understanding. Like Oedipus, my fate is sealed, and despite my best efforts and accumulated skills, I’m destined to want to gouge my eyes out when I look at my code in a future iteration.

The surprising insight is that doing this counter-intuitive thing – building a system based on partial understanding and fixing debt as I proceed – is better than using a waterfall approach. Beyond that insight, the debt metaphor does not help me make decisions on my project, particularly about when I should fix the accumulated problems.

Here is an example to show how frustratingly unhelpful the debt metaphor is for decision making. Imagine that you and I are on a team and we follow a waterfall process, so ur-technical debt and the divergence of our ideas and code is not something we have to worry about, and I propose that we do something that we both know is ugly. The ugly thing could be to use cheaper, less experienced engineers, use a design that only works in the short term, or defer a code refactoring. As we ponder the decision, we would consider factors like our company’s cashflow or market opportunity, and we’d weigh these factors against the ugliness of my proposal. How we decide has more to do with topics covered in business school than software school.

Now, imagine the same situation, except that this time we are following an iterative process and we call my ugly proposal “technical debt”. Does our decision making change? No. Does the debt metaphor tell us how much debt is tolerable or say when we must pay it down? Sadly, no, it just says problems will arise and will worsen if we ignore them. It’s critically important that teams manage the problems facing their projects, but the debt metaphor just alerts them that code problems will arise. It doesn’t say how long you can defer fixing your code, if you can tolerate code written by novices, how to decide if a hack is expedient, or when to remove an existing hack.

Tools Cannot See Ur-Technical Debt

Ur-technical debt is generally not detectable by static analysis. Despite that, there are plenty of tools that promise the world, and will examine your codebase and deliver a list of issues labeled “technical debt” for you to fix. Their output is a small fraction of the real debt because ur-technical debt happens when my ideas diverge from my code, and my thoughts are stubbornly hidden from static analysis tools.

Here are some examples of debt that I consider important, but are invisible to tools. Imagine you have an aha moment where you realize “oh, customers can have multiple shipping addresses” or “aha, not all the server-side work on this remote procedure call needs to be done synchronously”. These are much bigger issues than tangled module dependencies.

Tools can, however, find circumstantial evidence of divergence, for example that two source files are always modified at the same time. In that case, the tool detects my failure to consolidate an idea, because that single idea in my head must be translated into two different changes to the code.

The danger is not in using tools, but that the small, automatically-generated, tangible tool output will overshadow the huge, manually-generated, intangible list of debt that can only be identified by skilled developers. The danger is that we fix everything the tools tell us about yet never have code that is coherent.

What About Architecture?

Ward Cunningham’s original paper describes how his team evolved the architecture across iterations. Although many of their ideas came from requirements or the team’s experience, “… a third, and more tantalizing, category of objects only surfaced through a process we could call Incremental Design Repair. We found these highly leveraged abstractions only because we were willing to reconsider architectural decisions in the light of recent experience.” It’s important to realize that not everyone using the term architecture today means the same thing, and that was doubly true in 1992. The original paper argues that architecture trouble is just another example of debt that the team can fix iteratively.

With a few decades to reflect on this, my opinion is different. I see technical debt in a system’s software architecture as perhaps the worst kind of debt. Architectural decisions are wickedly expensive to change later, so it is usually worthwhile to spend some time up front (alas, waterfall-style) examining the full problem and trying to choose an architecture, if only to avoid dead-ends that are foreseeable with a bit of effort.

Treating the Root Cause

I’ve written this article because, over the years, I’ve been inspired and guided by the insight in Ward Cunningham’s writing about technical debt. As the term technical debt has become popular, I find that it’s been watered down and that developers are unaware of the original and insightful idea.

The good news is that most teams now follow some kind of iterative process. The bad news is that it’s often closer to sedimentary development, with high levels of ur-technical debt. The benefits of iteration aren’t free. Your iterative process must drive code alignment with ideas, otherwise ur-technical debt will doom your project.

Different symptoms require different medicine. Inexperienced developers? Try mentoring, career development, and challenges that encourage growth. Oblivious to architecture? Help developers recognize big balls of mud and teach them about design alternatives. Your boss wants a hack to meet a deadline? You must enter the world of management and evaluate the risks to the project or company. If the symptoms are that the code has become tangled, the team can’t meet new requirements quickly, and developers no longer enjoy one of the most intellectually rewarding occupations, then the medicine is iterative development done properly by consolidating ideas along the way. If we lump all of this under the umbrella of technical debt, then it’s hard to treat the patient.

When reflecting on the original paper, Ward Cunningham wrote that “… it was important to me that we accumulate the learnings we did about the application over time by modifying the program to look as if we had known what we were doing all along and to look as if it had been easy to do …” [2] This idea has stuck with me over the years like few others. How can we expect to have nice things if we don’t build them that way?

References

  1. Ward Cunningham, “The WyCash Portfolio Management System”, OOPSLA 92, Vancouver, British Columbia, Canada, Addendum to the Proceedings, Experience Report, 5 - 10 October 1992, https://c2.com/doc/oopsla92.html.
  2. Ward Cunningham, “Ward Explains the Tech Debt Metaphor”, February 14, 2009, https://www.youtube.com/watch?v=pqeJFYwnkjE and http://wiki.c2.com/?WardExplainsDebtMetaphor. (URLs accessed March 2020).
  3. Ward Cunningham, “Ward Cunningham”, IEEE Annals of the History of Computing, Volume 33, Issue 4, April 2011, https://ieeexplore.ieee.org/document/6096528.