This is the first in an occasional series on technical debt and ideas for avoiding it.

What is technical debt?

Technical debt is any code that's been written in an expedient manner that causes continuing problems of integration and maintenance down the road. These problems cost more and more as time goes on, as your codebase becomes more cluttered with band-aids, undocumented code written by developers who aren't here anymore, or even whole sections written using a different framework or coding standards by a contractor or as someone's experiment.

Sometimes you can't help but go into debt. Startups especially can be prone to tech debt, as they develop their quick initial MVP, then pivot once or a few times, always pushing for faster release cycles as the dreaded end of the runway looms closer and closer. They may have promised themselves that their initial code was just a throwaway prototype, but they never get around to writing the "real" version. Their whole codebase can end up on a very shaky foundation propped up by tons of patches and compromises.

Even established companies can get into too much technical debt. A project gets written by a third party whose code isn't held to the same code reviews & standards as the in-house projects. A manager decides his group needs to use a promising new MVC framework or other technology for their new feature, and it works well enough for version 1.0 - but then the champion and only real expert in this framework moves on to another company.

Why worry about it?

An interesting thing about technical debt is, these costs are just like interest on financial debt. All these expediencies and quick & dirty implementations may have saved some development time, but over time they cost more than if your company had focused on long-term quality in the first place.

The developers who are brought into a startup after the initial developers have left feel this "expense" all too often. It can be quite exasperating to join a dynamic, ambitious company who's poised to realize their vision with a freshly hired staff to complement the founding developer(s), only to watch your velocity plummet because you're constantly putting out brushfires and hacking your way through an uncharted mess of code instead of building the Next Great Thing.

Lack of documentation adds to debt

Documentation seems irrelevant when a program is first being developed. A brand new feature or module exists entirely inside the head of the developer. Once you've done the necessary design work - chosen the appropriate pattern to implement this business rule, roughed out the database schema and model classes to represent that new business object, sketched out the pages and decided which widgets to use, the feature is already created. In your mind, you're done: You have created an elegant thing of beauty. There it is, arrayed before you in its full glory. You're certain that you know each line of code already; all you need to do now is transcribe it, line by line, into actual code.

So who needs documentation? It's all there, obvious, for all to see, isn't it? And for the next few weeks, you may well be the one who fixes the initial bugs or adds a last-minute requirement; you'll be doing that based on your intimate knowledge of your creation, which is still fresh in your mind.

But at some point the maintenance will be handled by developers who don't have your deep wisdom and experience with that code. At this point, a large amount of the ongoing cost of maintenance comes from all the time it takes to figure out how the existing program works.

Therefore, documentation is essential, even when a program is first being developed.

It's hard to accept, but when we're developing a new feature, we must always remind ourselves:

  • "This code will spend more of its life being analyzed, debugged, repurposed, and extended by other people than I took to develop it.
  • These people lack the ability to read my mind; and the code will even outlast my time at this company.
  • Just because a computer program is a Turing Machine and I'm writing in a high-level language, it doesn't mean that anyone else is going to understand it."

Where am I?

Maintenance developers are often the new guys on the team. Putting the new guy to work on clearing out the backlog is a great way to get them familiar with the codebase without causing too much damage. Unfortunately they're usually thrown into the middle of it all and have to spend way too much time just finding their bearings in the code.

From bug ticket to template

The maintenance dev drops into your code by way of a bug ticket. Either a QA tester or customer support has reported an error on a page. Perhaps it's a formatting problem; or maybe it's a behavioral anomaly. Either way, all the dev has to go on is the URL of the page where the bug was seen, and maybe a screenshot.

In an ideal world, every page of your website would be built using a single framework, that enforces regular routing rules so that you can automatically tell where the controller & template is by looking at the URL. In the real world, your company may use several frameworks to implement different sections of your site. And your URL strategy may be quite messed up; with a different routing strategy for each of those frameworks, plus miscellaneous URLs that are there for "legacy reasons".

So the real-world maintenance dev brings up the page on the live site or their dev box, and opens up the browser's debugger. They must examine the raw HTML and search for an element that encloses the broken item, praying that the element has a unique classname or ID. Then they open up their IDE and do a global search for that classname.

If they're lucky, the classname is generated in only one or two places, and it's statically built instead of being constructed on the fly so the search finds it. If the system is an MVC system, this will be a template of some kind. It'll consist of HTML, surrounded by (hopefully) a minimal amount of logic.

Best Practice: Add HTML comments to each template to mark their beginning and end, and which source file created them.

Add these helpers to your in-house utility class. Replace $isDev with whatever you use to determine whether you're running in Dev, Test, Stage, or Live:

class MyCo_Utils { /** * Add an HTML comment to mark out where a snippet on the page * came from. * * @param $location string This could be __FILE__, __METHOD__, * or your own ad-hoc string to identify the template. * @param $controller string|object This could be a reference to * this template's controller, or an ad-hoc string to identify it. */ static function commentTemplateStart ($location, $controller = null) { if ($isDev) { echo "<!-- BEGIN {$location} -->\n"; if (is_object($controller)) { echo "<!-- CONTROLLER: " . class_name($controller) . " -->\n"; } else if (is_string($controller)) { echo "<!-- CONTROLLER: {$controller} -->\n"; } } } /** * Add an HTML comment to mark out the end of where a snippet on * the page came from. * * @param $location string This could be __FILE__, __METHOD__, * or your own ad-hoc string to identify the template. */ static function commentTemplateEnd ($location) { if ($isDev) { echo "<!-- END {$location} -->\n"; } } }

Call these methods from every template:

<?php MyCo_Utils::commentTemplateStart(__FILE__); ?> <?php /** The template's docbloc */ // Escape some output values, set some flags, whatever minimal logic // there needs to be inside the template ?> <!-- Raw HTML output, with <?= $insertionValues ?> --> <?php MyCo_Utils::commentTemplateEnd(__FILE__); ?>

From template to controller

If your page or widget has a behavioral problem, then you'll probably need to figure out which controller is calling this template. Again, if your whole website is built with a clean framework with straightforward routing rules, it may be obvious where a given template's controller is. But in the real world you'll probably need to do some sleuthing. Depending on how hobbled the page is by its legacy debt, this could get tedious, when it really should be obvious.

If your template is a PHP file, you can place a breakpoint at the beginning of your template and then trace through the call stack to find out which controller called it. If it's a templating language script like Mustache, then you'll have to pass it the name of the controller manually. Our utility code above makes this easier.

Best Practice: Add HTML comments to each template to mark their controller.

When you invoke a template from your controller, you should pass along either a reference to the controller class or its filename & linenumber (i.e. __FILE__ . ":" . __LINE__). Using our helper code above, you can pass it in as the second parameter when you call commentTemplateStart.

<?php MyCo_Utils::commentTemplateStart(__FILE__, $this->controller); ?>

Some technical debt is unavoidable. Fundamentally, the cost of tech debt comes in how badly it slows down the velocity of future development. Ideally you pay down technical debt through refactoring and even rearchitecting your site's code. But in the meantime, a good commenting discipline that makes it easier for future developers to get up to speed on your code can be a surprisingly cheap and effective way to tamp down some of that debt.

Your thoughts?

How many different frameworks & templating languages does your shop use to build its pages? How complicated are your site's routing rules? How often do you have to tediously follow clues in the raw HTML, then do a global search, just to figure out where the code is? What are your techniques for adding signposts in your code to orient the maintenance developer to their surroundings so they can quickly get to investigating the root causes of bugs?