We′ve all seen that scene in the movie where the family all gather round to hear the reading of the will. As the solicitor reads each name aloud, the expression on the corresponding face is a signal as to how welcome the bequest is to the recipient A smile, a gasp of pleasant surprise or, maybe, a frown of disappointment. A legacy is something that is left or given by someone who is now at a distance, making communication with the donor difficult. It is also an ′aide-memoire′, something to remember them by.
Recently though, the term ′legacy code′ has come to mean code which is difficult to work on, partly because it can be difficult to communicate with the originator of the code but mostly because it is difficult to communicate with the code itself. This strikes me as strange because that is the very essence of the programmer′s task. We are not ′problem-solvers′, as some would have it. Calculating the V.A.T on an invoice or working out how to draw graphics on a screen are not ′problems′ and there are usually domain experts on hand to help us with those little puzzles anyway. We are communicators, our task is to receive the communication of intent from the user, transmit that intent to the compiler and also transmit that intent to future developers. When you consider that maintenance is probably the greatest proportion of total development cost, you would think we might make a little extra effort here.
Not long ago the ghost of Marley (Jacob that is, not Bob) visited me. He took me to a software development company that is doing quite well financially and had recently purchased another company to fill a hole in its product portfolio. On acquisition of said company, the development team took delivery of the source code for the product and in doing so also accepted responsibility for its maintenance. They soon found out why the company had been so readily available for purchase. Although a mature product, the application was riddled with defects and the user base was declining rapidly because no upgrades had been released for over a year and there seemed to be no prospect of one in any immediate future. In addition, although the code was documented, both externally and internally with comments, it looked like no attempt had been made to update the documentation when the code had been updated, thus making the documentation worse than useless. There were tests available for the code but these were in form of written test plans that the previous company′s QA department had performed manually and again there was no guarantee that these bore any real relationship to the current codebase. In line with what seems to be standard take-over procedure nowadays, the staff of the acquired company had all been made redundant and so were unavailable for consultation. The programmers charged with the task of fixing the defects were scared to even go near the code, it was so badly structured.
This is the ghost of software projects past. What can you do in this situation? Well, the first thing I suppose everybody would suggest is to get the most serious defects fixed, then maybe start thinking about documenting the code, perhaps even reworking it and introducing some structure into it. But how? Changing the code may cause any number of knock-on, or domino, effects. Without proper documentation it is impossible to know what ramifications any change may have. At the same time, it is extremely difficult to document code that is badly structured. A chicken and egg situation!
The only real solution is to develop a test harness. We can develop a unit test harness that demonstrates the current behaviour of the system. Once we′ve done this, we can improve the design and structure of the system with confidence, knowing that if we change the behaviour, i.e. introduce a defect, the tests will tell us immediately and we can go fix it. We′ll know when it is fixed because the tests will all pass again. Changing the design of existing code without changing the behaviour is known as Refactoring, a technique which is becoming more and more popular. Tests that demonstrate the current behaviour of a system are known as characterisation tests.
Similarly with the defects in the system, we can write characterisation tests that demonstrate the current, faulty, behaviour of the system. Next, we alter the tests so that they demonstrate the correct functionality and they should fail when we run them. Then we alter the code to make them pass. In this way, we have step-by-step proof of our progress.
This is hard work! Make no mistake about that, It is hard work and it takes a long time because we have to do it bit-by-bit, incrementally. We start off with very granular ′smoke′ tests that ′ring-fence′ large pieces of functionality, before becoming more detailed as we focus on individual features. It is very difficult to write tests for code that wasn′t originally written to be tested but any other approach is fraught with danger. How else can you alter code without knowing whether you have introduced more problems? Remember what we said? Legacy code is an aide-memoire. You don′t forget this type of project in a hurry.
The next time old Jacob′s ghost called, he told me about a spacecraft, whose mission is to travel millions miles through space on a journey that is expected to take ten years at which time the spacecraft will rendezvous with a comet with the intention of investigating it. Until it reaches its destination there is no way of knowing what the environment will be like, what features the comet has and how it should be investigated. More importantly, until it reaches its destination there is no way of fully testing the software, sure we can run simulations, etc. but we all know that there is always something that will be missed no matter how diligent we are. The only answer is to have a team of programmers that are fully conversant with the software on stand-by for when things go wrong but this raises another set of problems.
The developers currently working on the software for the mission are postgraduate students, they′re all expected to leave university to further their careers in industry within the next two years. It is extremely unlikely that any of them will be available when the spacecraft reaches its destination. Even if they were it is unlikely that they would remember the workings of the software anyway. So the problem is how to communicate the functionality and intent of the software in a way that is complete and unambiguous, to a team of developers who have no prior knowledge of the system. This was my introduction to the ghost of software projects future.
This is an easier problem to solve but the answer is related to the solution for software projects past. What if we use test-driven-development to build the system? We capture the user requirements as a set of automated acceptance tests and then only write just enough code to pass all the tests? The tests could be run at any time and they would tell us the current behaviour of the system, they would be an executable requirements specification. Wouldn′t that also mean that we have one hundred percent test coverage, since the tests were written first and the only code written was code to pass the tests? We would have a set of characterisation tests that we can bequeath to our descendants to make it easier for them to modify the system and it will be easy for them to write new tests to support the modifications because the system was written with tests in mind.
If we did exactly the same at the unit test level, write tests and then only write code to pass the tests we would end up with a unit test harness that specified the functionality of the system on a module-by-module basis. We would then have two sets of executable documentation; a requirements specification telling us what the user wanted the system to do and a design specification telling us how it is intended it to do it. Both sets of documentation would be completely current and can tell the user exactly where any problem lie, in seconds, just by executing them. Unlike the previous project, this is one that you can forget about because you have confidence in the documentation.
Since then, the ghost has been with me constantly. Week after week we visit companies that are industriously producing legacy code today. Day-in, day-out, too busy failing now to provide for success in the future. Wasting money generating documentation that bears no relation to the finished system, managers refusing to allow developers to write their own tests, developers desperately trying to maintain stovepipe systems whose design and structure are understood by only a few key individuals. Project managers frantically attempting to cling to schedules in the face of continually changing requirements. Truly these people are troubled by the ghost of software projects present.
In some cultures legacies are used as a weapon. Where the rules of the culture do not allow refusal of a legacy, bequeathing to someone something that is more expensive to maintain than it is actually worth can make the recipient bankrupt. This is where the expression ′white elephant′ comes from.
The white elephant is a rare breed, in some cultures it is held sacred and may not be harmed. As they are tremendously expensive to keep it became custom to give them as gifts to ones enemies. The recipient is then saddled with the burden of maintaining the precious animal, depleting his resources dramatically and leaving him less of a threat. The phrase ′white elephant′ has now come to mean anything that is more expensive to maintain than it is worth.
We are sometimes our own worst enemies, producing our own white elephants and then refusing to recognise the futility and cost of striving to maintain them. It takes a brave man to go against tradition and dispose of the white elephant. It takes an even braver man to go against tradition and refuse to accept the white elephant in the first place.
White elephants are gifts for your enemies, don′t give them to your colleagues � you will be remembered for it.
First published in Application Development Advisor