(From IEEE Software)
Bookshelf
Guiding the Future of Legacy Code
Adam Geras, Michael Smith, and James Miller
Working Effectively with Legacy Code by Michael C. Feathers, Prentice Hall PTR, 2004, ISBN 0-13-117705-2, 456 pp., US$44.99.
Legacy code is code without tests.” This is the first in a series of eye-opening statements Michael Feathers makes in Working Effectively with Legacy Code. Perceiving legacy code from this angle has huge implications—the source code’s structure isn’t the problem, it’s the code’s structure and the need to revise it to meet new or changed requirements. Instead of focusing exclusively on improving poorly written code, Feathers adopts a practical attitude toward changing existing code so that tests characterize the runtime behavior and code quality improves as a matter of purpose rather than aesthetics. This lets him develop an effective guide for professionals dealing with the difficult situation of modifying existing code with minimal or no tests—particularly those highly motivated to improve the situation and leave a positive legacy.
Style and organization
The book’s style and tone are quite similar to those of Feathers’ workshops—well-organized, thorough, and practical. He doesn’t spend a lot of time storytelling, and there are few belly laughs. His attitude is more like, “Let’s get busy. Let’s make software easier to work with so that we can change it with confidence. Now that the going has gotten tough, this is how the tough get going.”
Working Effectively with Legacy Code has three parts. Part I (five chapters) describes the “Mechanics of Change.” This includes a critical summary of change points—points in the source code where changes might be made and tests written to support the change. Parts II and III (20 chapters) form the book’s reference section, with each chapter focusing on a different maintenance or change situation. We were relieved that Feathers avoids the temptation to come up with cutesy names for each of these situations, opting instead for longer, more descriptive titles. For instance, Chapter 12 is titled, “I Need to Make Many Changes in One Area.” This approach is much more useful than might be, say, something like “MultiChangeHotSpots.” Part III is a catalog of dependency-breaking techniques that Feathers uses throughout Part II to deal with the situations he presents. He intends for you to use these last two parts in a “pick-up, put-down” mode, reading the chapters in Part II in any order you please and the techniques in the Part III chapter sections as you need them. We strongly suggest reading Part I from start to finish because it introduces key terminology and concepts that Feathers uses throughout the remainder of the text.
Feathers positions dependencies as the primary roadblock to working effectively with legacy code and seams as the primary mechanism for introducing changes that break these dependencies and allow the insertion of tests. This aspect of the book is particularly well done. If you weren’t sure before, this is where Feathers’ practical style and tone convinces you that he knows his material and gives you more confidence in following his advice. However, we think that he should have more heavily stressed that his techniques are equally applicable to new code with insufficient tests.
Readers’ different perspectives and approaches
We each read and discussed Part I and came away with quite different impressions. One reviewer, a business application developer with extensive testing experience (both test-first and test-last), found that Part I met his expectations and was enthusiastic about reading more. Another reviewer, with considerable experience as an embedded-systems developer, was looking for explicit examples demonstrating what the first move should be in adding developer tests from Feather’s nonscripted CppUNITLite test environment, or customer tests using Framework for Integrated Test (FIT), to an existing system. He expected more basic tools-oriented advice and instruction. Nonetheless, we all agreed that formalizing the logistics of changing software has benefits, and Part I does this concisely.
If you’re looking for assistance in using, say, an xUnit testing framework, then you’ll need to take a different approach when reading this book. After Chapter 5, stop and defer further reading until you’ve figured out the tools. Then, when you’re hungry for ways to effectively use the tools to improve your programming situation, continue. As with many other agile-oriented books, Working Effectively with Legacy Code limits the tools discussion to xUnit for unit (developer) tests and FIT for higher-level (customer) tests—possibly not a full representation of the testing tools market. In the real world there are, unfortunately, fewer organizations that have adopted automated customer testing than have adopted developer testing, so rallying around a single tool, such as FIT, can help us build up a set of practices and patterns that we can share. At the same time, limiting customer tests to FIT generates tunnel vision and could lead to a risky long-term assumption that the problem of cheap, effective automated customer testing is already solved.
We recommend this book for those developers eager to adopt test-first thinking in their work with existing code bases. Even for those working on new applications, portions of the code will have minimal or no tests, and the techniques outlined in the book will be instructive in bringing those portions under control. That this book formalizes techniques that are probably widely used is an important contribution in itself. We believe this book will be useful to many developers and look forward to Feathers extending these ideas, in his own distinctive style, to characterize legacy system behavior and requirements through customer tests, before upgrading and modification.
Adam Geras is a PhD student at the University of Calgary. Contact him at ageras@ucalgary.ca.
Mike Smith is a professor at the University of Calgary. Contact him at smithmr@ucalgary.ca.
James Miller is a professor at the University of Alberta. Contact him at jm@ee.ualberta.ca.