Wouldn’t it be great to start a project from scratch? Use all the latest technologies, design and write the software TDD-style, as the creator intended? Well… yes, but this isn’t your dream world, this is reality. And here we have to deal with existing software (aka legacy software) most of the time. Unfortunately, this software usually doesn’t have an extensive test suite that allows it to be refactored with every change. More often than not programmers take great care not to touch any existing code when adding new functionality or fixing a bug in order not to risk breaking any functionality. This, of course, is to the disadvantage of design and maintainability. And so software rots, a little bit with every change. The more rotten it is, the harder it is to bring it back on track and clean it up again.
What to do?
So, what are you supposed to do? Cleaning it all up is a way too daunting task and would probably take longer than writing the whole thing from scratch again. Management will never agree (for good reason). So, don’t even think about it, it’s not an option (in general, there are of course exceptions). The way to go is to follow the boy scout rule: “Leave the place cleaner than you found it.” If you follow this rule consistently you will clean up the code bit by bit, change by change. And more than that, you will clean up the most important parts first – the parts that change most often. In other words: If it took you x hours to understand the bloody mess, don’t make it x+1 hours for the next poor soul that comes down this track.
How do you do this exactly?
Here’s how I usually attempt making a change to legacy code.
- Find the area (class(es), method(s)) you need to change.
- While you are trying to understand the code rename variables to more expressive names than ‘m’ and ‘m2’ (resist to change any behaviour just yet).
- Once you understand the input and output of the area you need to change, write (unit) tests that cover the existing behaviour. This might be hard since code developed non-TDD-style tends to be not easily testable. Try anyway.
- Once you got sufficient code coverage, refactor the hell out of the code to transform it into a better design and make it more understandable (e.g. every comment is an invitation to extract a method with a name conveying the message of the comment.) It is of course debatable what sufficient code coverage is. For me it means that I am comfortable with making changes to the code without worrying about breaking existing functionality.
- Write a failing (unit) test that uncovers the bug you are fixing (bugfixes without tests are anti-fixes) or the missing functionality you are adding.
- Fix the bug/add the new functionality.
- Feel good about yourself.
- Make sure everybody on the team agrees upon making an effort to not letting code rot and that their is a common standard of code quality. This is probably the harder part. I am still working this one out myself.
- Life happily ever after