Preventing Software Rot – Maintaining Code Quality & Sanity Over Time

Maintaining software is hard, and it gets harder the longer software is around and the more hands it has touching it. Complex and distributed systems require even more care to keep them working and malleable.

Here are the things I focus on to help maintain the quality and sanity of a large code base over time.

1. Scrub Your Requirements

When specifying a feature of an application, it’s tempting to jump to the implementation. It’s natural to think in terms of things we have seen in other applications and want to mimic them. Too quickly, we jump to the how–and forget the why.

When specifying features of an application, it is best to be clear about the why. Implementations change, or some new widgets come along.

If you have quantitative requirements, such as response time or frame rate, make sure that you have the means to measure them before you commit to them. Lacking well-defined goals or a means of measuring your progress toward those goals can hinder your progress and lead to over-engineering and more complexity to maintain.

2. Automate Tests and Run Them

If you are taking the time to verify that something works, ensure you have a strategy to keep it working. Start every project with an automated test suite for any application or library you are releasing to the world.

Don’t just say you will get to it once your project gets off the ground. Commit to it right from the start, and get a test suite running and monitoring the health of your project from the get-go.

Over time, and as complexity grows, it can be difficult to keep integration and system level tests running and passing. Although it can be tedious to stand everything up and get all of the pieces working together, it is worth the effort.

We naturally like to maintain a small scope while we crank out a new feature and weave our way through the labyrinth of our code base. While it is great having tests to validate the nitty-gritty details, high-level tests are the real glue that holds it all together.

3. Trim the Fat

If pieces of your project are no longer necessary, they should be eliminated. Supporting old behaviors costs money and energy, and it allows the complexity of your application to grow.

Many times, we will over-engineer parts of our systems as defensive measures when we are trying to hone in on a solution. When you end up where you need to be, look back at the trail you have left. Remove the effects of getting sidetracked and fixing things that may have not really been part of the problem. Simple solutions are are not only easier to maintain, but easier to understand for future residents on your project.

4. Leave Breadcrumbs…

Accept that code, on its own, is not self-explanatory of its intent. Before anything else, your tests should clarify the why. The how is likely much more easily grok-able.

If you have to do something nasty to fix a problem, leave a comment explaining why. You may even want to leave an apology for the poor souls who have to run into your solution months or years later.

Be honest. Leave TODOs or even HACK tags in your code. Of course, you should try to eliminate them before you roll out the project, but we all know that shit happens.