I’ve seen it far too often: an application is initially designed to perform a small set of tasks, but over time, more and more functionality is added. What should be a success (new features are requested because the existing ones work and are useful) ends up becoming a real hot potato: impossible to maintain, with every new change requiring more effort and money.
I’ve also seen—and experienced—it far too often: the people responsible for these applications are reluctant to acknowledge the problem. They tend to deflect blame and remain in a state of chaos rather than professionally admitting that they’ve reached a critical point where difficult decisions have to be made.
The result is always the same: after a great deal of frustration, it inevitably becomes clear that entire parts of the application have to be rewritten, if not thrown away completely and started from scratch.
We need to keep things in perspective: if it’s a hobby project, a repository you barely maintain on GitHub or CodePlex, that’s fine; the only thing you may have lost is some time. But if it’s a business project, all of the above translates into painful costs and, most likely, a frustrated team working on a difficult application.
Here’s a mantra we should never forget: all software will inevitably need to change (and that’s the foundation of the agile approach). If it’s not designed to accommodate change, sooner or later we’ll end up with a corrupted application and the situation described above.
The good news is that this can be avoided by taking the right approach from the start of the project.
But why does an application become corrupted over time as we make changes to it? Here are some of the reasons:
- An application is initially conceived as simple or small, then forced to grow very quickly and without control.
- There’s no methodology for introducing new changes: requirements arrive from one day to the next, work is often undone, and there’s no planning or culture of testing and validating the application before delivery. This creates fertile ground for chaos.
- Requirements aren’t clear: business requirements aren’t translated into technical requirements accurately or with sufficient understanding. As a result, functionality is implemented that isn’t quite what was wanted, triggering a cascade of changes and possibly leaving dead and obsolete code behind to clutter the solution.
- A scattered mix of technologies is used, making the application harder to evolve. To write good, maintainable software, we need to know the libraries and frameworks we use well. Too many of them in the same application cause confusion and make it harder to work with. Complex libraries can also obscure the solution.
- No time is spent improving what already exists, even when it works. Software development is incremental to some extent: we build new functionality on top of what’s already there, or follow the same patterns. We should always be looking for ways to continuously improve what we have.
- Obsolete, poorly designed, or inadequately tested code is kept at all costs. Sometimes it’s hard to remove parts of an application because we remember how much effort they took to build, or simply because we’re reluctant to do the work. But carrying something flawed forward is harmful. We can say that what we remove has, in fact, taught us how to do it better.
- We forget that it’s not just the code itself that needs improvement; the design and organization of the project do too.
- Clean-code and refactoring practices aren’t followed consistently.
- The project has no tests, or its test suite is insufficient and code coverage is low. This means we don’t know soon enough which bugs we introduce when making changes. The sooner a bug is found, the easier and simpler it is to fix. Writing tests also forces us to give the application a better structure (testable software), moving us away from monolithic, rigid approaches.
- The design, if there is one, is poor.
- And, of course, some or all of the things I describe in The Programmer’s Black Book happen.
How well or poorly we do all of the above determines how soon we reach the maintainability barrier, as described by the Boehm curve or law. It illustrates how the incremental cost of fixing bugs increases when they’re found late in a project rather than early on, when the software has already become too corrupted:

Note: This article is a summary and part of the work-in-progress project The Practical Agile Programmer, scheduled for release in July 2018.