Skip to main content

Code Maintenance Requires More Than Testing

Writing and running automated tests can go a long way to help maintain the quality and reliability of a code base. Tests help ensure the code you've written executes the way you expect it to. However, even though automated tests are a great tool for helping to ensure quality over the years in which people will contribute to the code, they are not sufficient in and of themselves. It may be inevitable that all software products will reach a point of obsolescence no matter how much work is done to keep them current.

The inherent problem is that if we view tests as constraints upon what the software system under test can be, then as we add more and more tests over the years, we will have more and more constraints on the shape our software is allowed to take. It then seems we must reach an inevitable point where we can no longer change our software system without violating a constraint, i.e. causing a test to fail. In more practical terms, we'll more likely first reach a situation where it requires too much effort to be worthwhile to make any substantial changes while still satisfying all of the constraints. There may not be a way to avoid this situation in the long run, but if we want our software to have a long and happy life, we can take steps to prolong it until it reaches this unhappy fate.

One of the best ways to extend the life of your software is to take make use of one of the other advantages that tests can provide: the ability to aggressively refactor code with some confidence that the refactoring has not caused regressions in functionality or reliability. If you have a good, thorough set of automated tests, they will inform you when changes you make to existing code introduce new bugs or break or remove existing features. This gives you the ability and confidence to rewrite existing code whenever you see an opportunity to improve it.

Once we've established the ability to rewrite existing code, the question remains why would you want to do so. If it ain't broke, don't fix it, right? You can certainly make an argument for leaving functioning code alone, particularly when you have so many other tasks to tackle. Especially when those other tasks involve adding new features that will provide real value to the software and its users.

Refactoring provides value as well though. It enhances the long-term maintainability of a software product. It paves the way for new features to be added in the future more easily than they otherwise might have been. Rewriting functioning code is a long-term investment in your software. Like all long-term investments, you pay a price up front, but you don't reap any rewards from it until later. That always makes it a harder sell, but like many long-term investments, refactoring can yield rewards several times greater in proportion to the initial effort.

One of the best ways I can illustrate this is with a case I encountered. We had some thread and database session management code in a code base I worked on. There was originally a very grand design for that code, but it never ended up working the way it was intended. Over time, it was modified, and its functionality curtailed, so that its ultimate behavior was relatively basic. However, the code itself was still very complicated, and very convoluted due to the evolution.

The code worked for the most part. However, on occasion, some change somewhere or deployment into a different environment would trigger a hidden bug. It was extremely challenging to debug and fix, as the person who had written the code originally had left the company, and no one at the company fully understood how the code worked. However, as it very rarely needed to be fixed, and as we all thought it would take a lot of effort to replace, we left it alone.

Finally, I had a little time and felt inspired, so I took a stab and rewriting it with a focus on making it as simple as possible. I boiled it down to the most basic operations the code needed to execute. At the end of three days, I had written a fully-functional replacement for the code that had plagued us with its inscrutability for so long. Not only did it fully meet the functional requirements, it proved to be over 6 times faster in execution.

The end result on the code size was that one implementation file was completely eliminated, another one was reduced by more than 90% in size and a third was cut by more than half. Thus, we ended up with a faster implementation that performed the same function with substantially less code, and not only was it less code, it was far clearer and cleaner code. This last part was critical because it meant that when someone needed to understand or modify the code later on, it would be much, much easier. We went from no one understanding this part of the software to everyone understanding, or at least being capable of understanding it.

This story illustrates what I think is the most important component of good code maintenance: removing old, unnecessary code. I particularly love the words of the writer Antoine de Saint-Exupery:
Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.
After a certain point in a software project's lifecycle, as it continues to age, the number of developers working on it will only trend in one direction: downward. While there is no hard and fast limit, conceptually it makes sense that there is some cap to the ratio of lines of code to software developers in order for a project to be well-maintained. The problem is that while the number of developers working on a project starts to dwindle, the size of the code base tends to increase.

While it may be difficult, if not impossible, on many projects to effectively reduce or even maintain the number of lines of code in a project over time, the least we can do is curtail the increase as much as possible. More lines of code equals more complexity and more complexity equals greater maintenance effort. Thus, eliminating lines of code whenever and wherever possible without causing regressions or loss of important functionality can go a long way in extending the maintainability of a software project.

Code is not sacred, it will evolve and change whether you want it to or not. If you don't exercise some control over the way it evolves, it will grow wild and tangled and become convoluted and hard to work with, comprehend and maintain. If you are vigilant in trimming back unnecessary branches, you can keep it neat, orderly and maintainable for many years to come.

Comments

Popular posts from this blog

Don't Build it Yourself

This post discusses the whether to buy or build software components needed for your application. The title should give you a clue as to which side of the discussion I land on. Someone Else's Code One of the behaviors that I've noticed is significantly more often exhibited by junior developers versus more experienced ones is the tendency to build everything from scratch. While I can appreciate the enthusiasm for wanting to create something better than has been done before, this is often simply a waste of time. For many of the software tasks you will want to accomplish, someone will have made a library of framework that does 95% of what you want out of the box, lets you configure or extend it to do another 4.9%, and you come to realize that 0.1% remaining wasn't all that important anyway. In fact, with the proliferation of open source software, many of these software packages are 100% customizable because you have access to all of the source code and can change it at will...

When All Else Fails, Use the GUI

In this blog post, I show you how I automated entering credit card transactions in QuickBooks using the PyAutoGUI package for Python. Automating routine tasks is a great way to save yourself a lot of time. Whenever I spend a significant amount of time doing a partiuclar task on a computer, I ask myself if there is a way to automate it. Generally, as long as the input to the task is located on the computer, the answer is yes. The kinds of tasks that evade automation are those that have some kind of physical medium as an imput. While there are technologies that could help automate those tasks, it is much more complicated and difficult to do so. For tasks that can be automated, my favorite language for doing so is Python. Python has a wonderfully readable, expressive syntax, and there are an astoundingly large number of libraries and packages available that make the job of automating jobs much easier. One of my favorite simple examples of how Python saved me a lot of time is when I...

Some Thoughts on Software Estimation

"All programmers are optimists." Fred Brooks's adage is as true today as it was when he first penned the words. The truth is that people are naturally very poor estimators. We all have a tendency to believe everything will go more smoothly than it probably will. Things will work out just as we planned, assuming we bothered to plan at all. I encounter the reality of this often in my work. The first deadline passes, and we assure ourselves that we can catch up to meet the next one. The next deadline passes, and we finally come to terms with the reality that we're not going to make it. Then, we push the schedule back a week, when if we really reflected on the situation, we would realize we were probably a couple of months out from actually completing the project. The product gets later and later one day at a time. Lots of yelling and finger-pointing ensues. Management demands better estimates, but rejects any that don't fit with their view of when we "sh...