by Matt Slot
Of course, most software engineers are not at the top of the food chain. If you are lucky, you get to do some of the gruntwork involved in shipping a new product. If you aren't, you probably get stuck maintaining legacy code. While some products are written once and never touched again (such as games), most real projects entail continued improvements for a version 2.0 and beyond.
It wouldn't be so bad if the original source was perfect -- but since that's unlikely (and quite embittering if you've spent days debugging someone else's "working code"), it's helpful to have some strategies for fixing and updating old code.
All parts of the code should be structured according to consistent coding standards, no matter how many people work on the project. I'm not talking about how many spaces to use or where the curly braces should go, because that's just eye candy. I'm talking about using descriptive naming conventions, so that you can look at a function name or source file and know what it does and where it belongs. (And if you think Hungarian notation is the way, then go for it.)
The project should have a well-rounded library of utility functions, and should make its library dependencies well known. Responsibility for memory allocation and deallocation, error detection and propogation, and many other mundane tasks should be spelled out.
Along these lines, a well-designed application will be modular -- a foundation of common utility functions, upon which each component is then built. Graphics, user interface, data management -- each maintained behind a well defined set of accessor routines. If you are thinking object-oriented, then you have the right idea. By keeping most of the implementation private to the outside world, you can always come back later and improve it -- or replace it entirely.
First and foremost, keep a good backup of the source code, and a log of each of your revisions. The best way to do this is to place the whole project into source code control, and religiously check in any edits with appropriate comments. This provides an edit-by-edit archive and change log from the previous checkpoint to the latest compile, and helps to track down inadvertant bugs introduced at any revision.
Next, you need to be comfortable with your text editor. It needs to have a rather flexible search and replace, and should be very good at comparing files or folders of files. Now, this doesn't mean I advocate blind search and replace operations -- quite the opposite. Nothing is as hard to undo as a poorly chosen global replace.
When you have a big list of changes to make, you should break them down into moderate chunks. Try to keep your changes local to a specific area of the project, so that you can learn and immerse yourself in that module and make all the necessary changes in one or two passes -- instead of revisiting it several times.
Major changes to the project structure or functionality should be planned so that they mesh well with the established framework. Ideally, you should start out by linking the new module to a simple tester application, so that you can work out most of the internal problems before linking it to the application as a whole -- which invariably brings out many subtle interactions.
Again, object-oriented design is a Good Thing (tm). The more you can seperate the interface from the implementation, the easier it is to replace the guts without many side effects ("gee, why is this code modifying that guy's global variable?"). If you do it right, you should be able to switch between the old and the new code at compile time by just changing a #define.
Finally, it's important that you try to avoid optimizations the core engine of the application unless you are intimately familiar with it. The critical sections of most software are already tightened up a bit for speed, at the cost of clarity. Source that has been massaged for the best compiler output and hand-crafted assembly are typically littered with under-commented edge cases. Even the original author may cringe at touching the code -- so you may be better off starting from scratch instead of losing time trying to fix all the side effects you added.
Now, I'll admit that I have strong reservations about diving into someone else's code, while other people can just skim along the top and make things work in a very minimalist way. While I envy their light-hearted approach, I don't really envy the subtle side effects they introduce. Invariably, people who aren't familiar with the code will apply patch after patch, making the code so much harder to maintain for the next guy down the line.
Legacy code isn't just gruntwork (although that's a big part of it), it's a chance to show up-and-coming coders interesting techniques in working code -- and to bring them up to speed if they haven't worked on any large projects. Be kind to legacy coders, because they are the next generation of lead programmers.
Matt Slot, Bitwise Operator