Several years ago, as a young programmer recently out of graduate school, I joined a small company and inherited a large amount of code that had been created by a previous employee. The code implemented a complex user interface and I had received several customer product change requests.
A sizable amount of the code had been automatically generated sometime prior to my involvement through the use of a user interface design tool. The tool allowed a non-programmer to graphically define the parameters of the user interface while magically generating working code on their behalf.
If any of you who have ever seen automatically generated code, you know that it’s hardly fit for human consumption. The code typically has poor structure, much redundancy, and no domain knowledge.
After the automatically generated code was created, other programmers had made additional code changes, which meant the design tool could no longer be used, resulting in a horrific mixture of poorly generated code liberally sprinkled with many hacked-in changes.
Now back to my role in this story; I studied the code carefully and found that I could scarcely make sense of it. I could sort of see how the other programmers had tried to fix various issues, but I had no luck determining how the generated code was fully operating. It became clear that any new functionality requests could not be developed using the existing code.
Next I made the same mistake so many young, overly confident developers make. I told my boss I’d work over the weekend and fix it. He looked at me over the top of his glasses and said, “You do that.”
To make a long and very painful story short, I threw away the existing code and rewrote everything from scratch with zero documentation on how the code was originally intended to function. The project impacted the company financially, operationally, and created a negative customer perception. Costs were tallied based on the missed product release deadline, numerous customer complaints, development time, missed opportunities, and some lost product functionality, resulting in frenzied hot-patching.
Today, as a much older and wiser (I hope) software development executive, I look back on this painful event knowing that my decision to rewrite the code caused a great deal of hardship, lost revenue, and costs. I also know that the code that I inherited had been so poorly maintained prior to my arrival that no other choice could have been made.
While I was clearly culpable, the culprit here is a concept known as code debt.
I invite you to read the Wikipedia article titled, Technical Debt. The article explains that code debt is commonly caused by: business pressures, lack of process or understanding, lack of building loosely coupled components, and lack of documentation.
The code I had inherited was literally a big pile of code debt. For years prior to my involvement, various programmers had been pushed hard by business stakeholders to make quick changes for near term and inexpensive customer requirements. The programmers didn’t really understand the code in its entirety as no documentation existed in terms of original requirements or change requests. Programmers simply hacked in changes without complete understanding of its effects. Additionally, no time was allocated to refactor the code into something better and more maintainable.
Well designed code that is documented, understood, and surrounded by good unit tests is relatively easy and safe to change. The weeks of development work I dedicated to whip the user interface into shape was literally the interest paid on the debt. I’d also bet that the other developers prior to my code overhaul had also paid some amount of interest simply by attempting to decipher the code.
The objective cost of the interest is hard to measure, but the subjective cost of code debt is often extremely high. Code debt is responsible for many cases of late releases, buggy code, under-performing code, non scalable code, high development costs, high test costs, unhappy customers, and abject project failure.
Just like real debt, code debt is easy to ignore. Stakeholders want to hear, “I can do that in two weeks” not “that will take four months.” Non-developers often take the “How hard can it be?” approach to product development. Developers are typically optimistic and want to please while customers want the new functionality tomorrow. All together, it’s a recipe for building up code debt. All involved have the tendency to say, “Let’s just do this quickly now and fix it all later.” Doing so adds to the debt and the debt grows in some non linear fashion.
The best way I’ve found to avoid code debt is to use an Agile process and a focus on the following principals taken from Extreme Programming:
- Coding standards
- Simple design
- Pair programming
- Continuous integration
Code debt adds to the complexity of resource planning. As IT professionals, we are continuously challenged with making better use of limited resources and focusing those resources on work that brings value to the organization and aligns with corporate goals.
I’d love to hear from you. What are your experiences with code debt and what have you done to prevent it from taking place in your organization?