Become a fan of Slashdot on Facebook

 



Forgot your password?
typodupeerror
×
Programming Books Media Book Reviews IT Technology

Working Effectively with Legacy Code 208

Merlin42 writes "I recently took a Test-Driven-Development (TDD) training course and the teacher recommended that I read "Working Effectively with Legacy Code" by Michael Feathers. First things first, a note about the title. Feathers defines "Legacy Code" a bit different than you may expect, especially if you are not into the XP/Agile/TDD world. I have heard (and used) a number of definitions for "legacy code" over the years. Most of these definitions have to do with code that is old, inherited, difficult to maintain, or interfaces with other 'legacy' hardware/software. Feathers' definition is 'code without tests.' For those not into TDD this may seem odd, but in the TDD world, tests are what make code easy to maintain. When good unit tests are in place, then code can be changed at will and the tests will tell automatically you if you broke anything." Read on for the rest of Kevin's review.
Working Effectively with Legacy Code
author Michael Feathers
pages 456
publisher Prentice Hall
rating 9/10
reviewer Kevin Fitch
ISBN 978-0-13-117705-5
summary Excelent overview of how to apply TDD to an existing project
Overall this is definitely an interesting read, and useful to anyone who has ever yelled "FSCKing LEGACY code!" It will be most useful to someone who already has some appreciation for TDD and wants to use it to 'pay down the technical debt' in a legacy code project. In my opinion adding unit tests (a sort of retroactive TDD) is the best ... err ... most effective approach for getting a legacy code project into a more malleable state.

One caveat is that most of the book is focused on working with object oriented programming languages. There is some coverage of techniques for procedural languages (mainly C), but this is not the main focus of the book. In a way this is unfortunate, since there is a lot of really useful C code out there gathering dust. But in the book he states that "the number of things you can do to introduce unit tests in procedural languages is pretty small." Unfortunately I would have to agree with him on this point.

One of the greatest things about this book is that it is written by someone who has worked with a lot of legacy code, and there are numerous real world anecdotes sprinkled throughout the text that really serve to help drive the points home. The code examples are plentiful, but not verbose. They all look like real code you might find lurking in a dark corner at work, not some fanciful made up snippet.

The high level goal of the book is show you how to write good unit tests for code that wasn't designed with unit tests in mind. The first step for writing unit tests is getting individual classes or functions into a test harness where you can apply known inputs, and check the outputs or behavior. To do this you need to break dependencies in the original code. The bulk of the book is dedicated to looking at different approaches to breaking dependencies.

Much of the book is organized like a FAQ. There are chapter titles like: "I Need to Make a Change. What Methods Should I Test?" and "My Project Is Not Object Oriented. How Do I Make Safe Changes?". This organization makes the book work a bit better as reference than as learning material. After the first few chapters there is very little flow to the book. Each chapter tends to stand as an independent look into a particular problem common in legacy code. As a result, you can read the table of contents and usually skip to a self-contained chapter that will help with the problem at hand.

The final chapter of the book is a listing of all the refactoring techniques used throughout the rest of book. So if you have a particular dependency-breaking technique in mind, you can skip straight to the description of the technique you want to use. This can be quite helpful when you need to perform a refactoring before you can get your code into a test harness. The descriptions are straightforward and provide a little checklist at the end that will help you make sure you didn't miss anything.

In conclusion I would definitely recommend this book to a colleague who is trying to introduce unit tests into code that was not designed with testing in mind. In fact I have already lent the book to several people at work, most of whom have bought their own copy.

You can purchase Working Effectively with Legacy Code from amazon.com. Slashdot welcomes readers' book reviews -- to see your own review here, read the book review guidelines, then visit the submission page.
This discussion has been archived. No new comments can be posted.

Working Effectively with Legacy Code

Comments Filter:
  • Whatever (Score:3, Informative)

    by Anonymous Coward on Monday September 29, 2008 @12:24PM (#25194857)
    Buy Martin Fowler's Refactoring [amazon.com] instead.
  • by DJ Jones ( 997846 ) on Monday September 29, 2008 @12:26PM (#25194879) Homepage
    Put all your changes in "int main()", use obscure variable names like xspatyc05 or funct123, always use static buffer sizes for any IO operations and under no circumstances should you add comments, it's a waste of time and no one besides you is ever going to have to understand it anyway.

    - I <3 Legacy code
  • encapsulation (Score:5, Informative)

    by Dan667 ( 564390 ) on Monday September 29, 2008 @01:07PM (#25195279)
    The most successful strategy I have had for legacy code that I have inherited is encapsulation of the old code into a new framework. I first attempt to build a black box wrapper with an API for what ever the legacy code did (wrap 5000 line loops, etc). Then as I can or need to change it, I take the black box and break it into proper libraries or readable functions (or start over). Have been able to do this for some really large bases of code and have a working system while I re-factored the mess a little at a time.
  • Re:Not exactly... (Score:1, Informative)

    by Anonymous Coward on Monday September 29, 2008 @01:17PM (#25195399)

    A tenant is someone who pays rent.

    A tenet is a principle or belief on which a piece of doctrine is based.

  • by Surt ( 22457 ) on Monday September 29, 2008 @01:24PM (#25195487) Homepage Journal

    No, testing verifies that your code works under certain conditions. A proof that your code is correct demonstrates that your code works under all conditions in a mathematically rigorous way.

  • Legacy Code (Score:4, Informative)

    by Orion Blastar ( 457579 ) <orionblastar AT gmail DOT com> on Monday September 29, 2008 @02:04PM (#25195939) Homepage Journal

    I have had good luck with legacy code, here is what I do:

    #1 Figure out what the code does and document it with comments and write a document on it.

    #2 Identify variables and objects and what they are used for and any naming convention the code may use.

    #3 You need to stick to the original style of writing or rewrite parts of it into your style if it can give a performance boost or make it more stable.

    #4 Try to find programming errors and things that do not make sense and rewrite them so that they make sense. Do error trapping and check for nulls and letters entered into number variables and all other sorts of things most legacy programmers overlook.

    #5 Work to make the code stable and not crash and run faster before you start adding new features to it. Users don't want to wait 15 minutes to do a report and then have the program crash after their wait.

    #6 Work with the help desk to identify the most serious problems that users complain about the legacy code. Make it a "wish list" and then fix each complaint as you have time to do.

    #7 Get direction from your managers, tell them what you are trying to do and any problems you have. You need to work as a team with other developers, the help desk, managers, and users to work out the issues with legacy code. Explain to them when you need more time and cannot make the schedule they gave. Make a deal with them to release a stable version but lacking features that might take more time than they thought to do. Tell the users you had to no add in those features to meet a deadline or ask them if they want to wait until you figure out how to add in those features.

    #8 Play Sherlock Holmes and read books or Internet web sites on the language and technology used with the legacy code. Search knowledge bases and blogs and forums for answers to solutions, sometimes someone else figured out what you are trying to solve. If not ask on a forum or blog or web site and see who answers. Many of my answers got that way from the Internet on legacy code, but management didn't understand why I spent so much time on the Internet. It was because they wouldn't buy me the books I needed and I had no documentation or anything to work with except for pure code with no comments and all with serious problems. Sometimes I had to spend 5 hours a day researching on the Internet and 3 or 4 hours coding, but in doing so I saved months of work, but management didn't understand that each web site I went to was work related and I looked at the design of sample code even HTML code to get ideas on how to solve the legacy code problems. Sometimes you have to call up a help desk of a vendor to get answers as well, but they docked me for long distance calls to Canada where Crystal Reports and Segate/Business Objects had their headquarters. Fixing Crystal Report errors would make me spend 5 hours a day on the Internet just to figure out what caused double lines in a report and why only certain users got it and not others.

    #9 When in doubt ask for help. Sometimes another pair of eyes can spot errors and mistakes that you cannot see. Diversity is a good thing with team members. Form a dream team of programmers of different backgrounds for best results.

    #10 When in danger, when in doubt, don't run in circles and scream and shout. Take a walk, get something to drink and relax. Take a mental health break instead of getting angry at other people for not helping you or not doing their jobs properly, they might be suffering from stress like you are and you don't know it. Be positive, not negative.

  • by mrchaotica ( 681592 ) * on Monday September 29, 2008 @02:48PM (#25196421)

    Pedantic (but this is Slashdot): "int main()" is not one of the valid forms for "main" allowed by the C standard

    ...

    The only standard forms are "int main(void)"...

    "int main()" and "int main(void)" are the same thing, you idiot!

    If you're going to be pedantic, at least try not to fuck it up!

  • by goose-incarnated ( 1145029 ) on Monday September 29, 2008 @03:07PM (#25196599) Journal

    "... When good unit tests are in place, then code can be changed at will and the tests will tell automatically you if you broke anything."

    Wrong.

  • by Surt ( 22457 ) on Monday September 29, 2008 @03:43PM (#25197007) Homepage Journal

    Either you misunderstood my post, or you have a fundamental misunderstanding of the halting problem.

    It says that no algorithm can decide in general whether or not a given program / input pair will halt or not. The emphasis on the in general is the key.

    It is actually trivial to do in many, many specific cases.

    http://en.wikipedia.org/wiki/Halting_problem [wikipedia.org]

    In particular, take note of the second sentence:

    Alan Turing proved in 1936 that a general algorithm to solve the halting problem for all possible program-input pairs cannot exist.

    (emphasis mine)

  • Re:Not exactly... (Score:3, Informative)

    by plover ( 150551 ) * on Monday September 29, 2008 @04:03PM (#25197217) Homepage Journal

    If the tests don't provide coverage for ALL the business issues that the piece of software is supposed to solve, then you pass the tests, but will have no clue if you broke or not things apart.

    That's not true at all. You have a lot of clues based on the tests that passed. You'll have confidence in the code that passed the tests. Overall, it's less to worry about. It's certainly of more benefit than no tests. And if you failed tests, you'll have 100% confidence that you broke something, and you can get it fixed.

    Consider the end goal of unit tests would be 100% assurance that all code paths are covered, and that all behavior is tested. Not that it's realistic in many places, but that is the goal.

    Now consider "Legacy Code" as defined by Michael Feathers -- code that does not have unit tests. You have no idea if a single line of it works, other than what your integration or end-user testing shows.

    His book is a set of procedures you can try to insert tests into untested or hard-to-test code. His strategy is fairly simple: jam a test into the middle of the code. Chop something in half, place tests on either side of the wound, and see if it still works. Now you have a big ugly gash separating two halves of the code, but you have less code to execute when running the tests on each half. Now, using that line as a starting point, begin refactoring the code, and adding unit tests to prove your refactorings are safe. Sure, you'll still need end user testing and integration testing, but your developers can move forward faster, and you're more confident that your new changes will improve the code, rather than break stuff.

    He's trying to help solve a hard problem: how do you refactor (which is inherently a test-driven-development strategy for improving your code) without tests that help ensure you didn't break anything? Once you've got tests, refactoring becomes easier. The more tests you have, the more assurance you have that your changes aren't breaking code. Ultimately, when you are 100% covered, you should be a lot closer to bug-free than you were when you started.

    I'll tell you right now that on a large legacy project, most people couldn't tell you where to begin placing tests. Michael's book helps jump start the process. And while the end game might not be 100% test coverage of all the legacy code, it leaves you a lot better off than you were before you started writing the tests.

  • by Anonymous Coward on Monday September 29, 2008 @05:22PM (#25198107)
    TestDrivenDevelopment [c2.com] on the original wiki (Ward Cunningham's). And a lot of the stuff it links to, and so on....
  • by zuperduperman ( 1206922 ) on Monday September 29, 2008 @07:30PM (#25199575)

    I agree with your points, but I think everyone here is missing a huge benefit of writing tests: by necessity it forces good design in and of itself because it's near impossible to test anything with with complex behavior. The result is that just to make the unit tests feasible, developers stop writing enormous monster classes without interfaces and start fragmenting things down to small units that do just one or two things with well defined behaviors.

    You see a lot of people complaining that tests are "too hard" etc., and in most of these cases it is because the software they are testing is poorly written - huge monolithic chunks of behavior with large dependency sets.

    I disagree with you about integration tests: I think they are as essential as unit tests. In fact, I think automated tests all the way up the chain to UI driven end product tests are the best way to do things.

"Protozoa are small, and bacteria are small, but viruses are smaller than the both put together."

Working...