On our project we have “code debt” as a work item on every release. When our customer asked us what code debt was, below is the answer I gave. I thought this might be of interest to other developers out there in the blogosphere…
Code debt work is largely aimed at reducing future development costs. It’s like doing an oil change on the code–if you leave it for too long, you’re going to end up with a big expensive problem on your hands–it’s most efficient to do a bit of code debt work in every release rather than letting it pile up. Code debt is usually not directly tied to specific features.
I have seen two extremes for dealing with code debt. The one extreme is the “big up front design” approach. In my experience, this approach tries to anticipate the axes along which features will need to be generalized and configurable before actual requirements for generalization and configurability are known–resulting in a lot of complexity and layers in the application that aren’t actually needed–and the high maintenance costs that result from that extra complexity. The other extreme is working for years on a code base without paying any heed to code debt. This can happen due to a continuous stream of aggressive deadlines or due to lack of automated test coverage (which prevents developers from refactoring for fear of breaking existing functionality). This results in a different kind of complexity–what I’ve heard called “spaghetti code” or a “mudball”–and the high maintenance costs that result from an incoherent code base.
Intelliware strives to reach a middle ground. At the start of a project, under the guidance of an architect, the developers will form an “architecture seed” that codifies the rules we all agree to develop by. Where possible, we create tests that enforce these rules. These are rules that enforce clear separation of concerns in the code modules (e.g. control, model, view). Beyond this architecture seed, we then approach each feature with the mindset of coding the “simplest thing that could possibly work”, avoiding the temptation to code generalizations or configurability in anticipation of some imagined future need. Code just the feature, and no more.
In following the “simplest thing that could possibly work,” over time certain inefficiencies emerge in the code. This is code debt. For example, we might code three pages that look and behave very similarly. At a certain point, it will make sense to consolidate the code that manages those three pages, reducing the amount of code, points of failure and therefore future maintenance costs. Another example is that for the first type of message we process it directly, but once we are up to 3 or 4 different kinds of messages, it becomes cleaner to add a “message handler” layer to the code.
The way we track code debt on our project is with cards on the white board. As we are coding features for a release we will discover code debt. Rather than yielding to the temptation to stop work on the current feature and “clean up” something we think is messy, we will write a card for it, put it up on the “code debt” white board, and then discuss its priority with a team later.