| Exploring Solution Spaces © Copyright 2003-2006, by C. Keith Ray | ||||||||||||||||||||||||
|
Archives
Subscribe |
2003.Dec.29 Mon Your team is handed a huge pile of legacy code, to enhance or maintain. What are the first things you should do? First, get it under source-code-control, if it isn't already. Second, build it and run it. If it has tests, build and run those. Third... review the code. Since your team is unfamiliar with the code, they will be reading it all anyway, just to figure out what it does and what they have to modify. It would be wise to organize that effort. If you can get the original writers of the code involved in the reviews, that will help, as long your company culture is mature enough to do reviews without blaming, put-downs, or criticisms that sound like attacks. For agility, don't print out sources and hold whole-team reviews away from the computers. Have the team do the reviews in pairs and trios at computers that can build and run the source code. The reviewers should draw rough diagrams on paper to help document their understanding of the code, and have the reviewers present their findings to the whole team in verbal summaries. Unlike reviews written up in the computer science literature, these are not reviews to find bugs, though no doubt with most legacy code, you will find lots of bugs. Don't fix them yet! The intention is to try to understand how the features are implemented, where the data flows (inputs, modification/processing, outputs), and what classes or functions are responsible for various things. When you find a possible bug, add a comment with an easily searchable tag, and/or add an assertion. Careful renaming of functions, files, and classes can be done here, when you identify names that don't explain the intentions of the code. If there are no layers, moving files/classes/modules into "Domain", "Infrastructure", "UI" and so on can help with understanding their roles. You will probably identify lots of redundancies - several classes, modules, or functions that do the same thing. Move those things closer to each other in the source code directories and files, but don't yet do any refactoring that changes the function-bodies or class structures. You and your team should identify which functions/classes/modules would be the "preferred" ones to use for new code, and which ones you will avoid using (and eventually refactor away). Identify and remove dead code, but be sure it's actually dead - if you're using a static language like C or C++, having a successful compile, link, run usually indicates that you didn't remove any live code. You want a stable foundation on which to build your modifications, so now write unit tests that detect the bugs you identified in the reviews. Work on one bug at a time, with minimal refactoring to make the code testable. Fix the bug, and test the entire application to make sure the fix doesn't have any side-effects. Fixing uninitialized variables can create unexpected side-effects because the code may be relying on the (coincidental) value that the uninitialized variable had. If you have requirements documents or some other description of the desired features, review those and the running application with your Customer (if you have one) to find out which features are actually present and working as desired, and what new features are desired. Use XP-style story cards and release/iteration planning for new features (or features to be reworked) and if possible, write automated acceptance tests and unit tests when implementing the new features. If you don't have QA people to write the acceptance tests, write them yourselves. All this advice supposes that you have some time to do the right things. If you don't have time to the right things, plan to spend time fixing bugs and stepping through the debugger trying figure out what's going on -- which will probably take longer than doing the reviews and bug-fixing up-front. Measure your progress against estimates - sometimes it's better to throw away bad code than to try to fix it, but that decision must rest on measured costs. Throwing away code doesn't have to an all-or-nothing situation. Replacing home-grown string classes with a standard string class, (ditto for container classes) and so on can increase the maintainability of the code without significantly affecting the domain-related design. |
|||||||||||||||||||||||