Working Effectively with Legacy Code

Author: Michael C. Feathers
4.5
All Stack Overflow 260
This Year Stack Overflow 5
This Month Reddit 3

About This Book

Is your code easy to change? Can you get nearly instantaneous feedback when you do change it? Do you understand it? If the answer to any of these questions is no, you have legacy code, and it is draining time and money away from your development efforts.

 

In this book, Michael Feathers offers start-to-finish strategies for working more effectively with large, untested legacy code bases. This book draws on material Michael created for his renowned Object Mentor seminars: techniques Michael has used in mentoring to help hundreds of developers, technical managers, and testers bring their legacy systems under control.

 

 

The topics covered include

  • Understanding the mechanics of software change: adding features, fixing bugs, improving design, optimizing performance
  • Getting legacy code into a test harness
  • Writing tests that protect you against introducing new problems
  • Techniques that can be used with any language or platform—with examples in Java, C++, C, and C#
  • Accurately identifying where code changes need to be made
  • Coping with legacy systems that aren't object-oriented
  • Handling applications that don't seem to have any structure

This book also includes a catalog of twenty-four dependency-breaking techniques that help you work with program elements in isolation and make safer changes.

Comments

by [deleted]   2021-12-10

Buy this book: https://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 Read it. Love it. Live it.

I was in this exact position. I was treated as a "savior" for setting up automated tests and refactoring an existing piece of software into a half-decent work so I could add new required functionality and save the company tens of thousands of dollars. (really, I was the only one persistent and naive enough to try to do it)

Standing up an automated test system can be a pain. Once it's up and in CI and easy to write new tests, people will use it if it will help them, and you can start writing unit and integration tests at least for new features.

Write larger scale acceptance tests around larger blocks of code (e.g. possibly using full program runs on data transformation programs). Use this to prove you're not breaking this as you set up tests FIRST and then cut out pieces one at a time. You have to use acceptance tests as a starting point to do large refactors and break down this sort of mess before you start pushing down into integration and unit tests.

by IxD   2021-12-10

https://www.amazon.de/Working-Effectively-Legacy-Robert-Martin/dp/0131177052?ie=UTF8&tag=googdemozdesk-21&hvadid=323934261977&hvpos=1t1&hvnetw=g&hvrand=10757532144450521743&hvpone=&hvptwo=&hvqmt=b&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=9072483&hvtargid=dsa-19959388920&ref=pd_sl_35q8ibnq4w_e&language=en_GB

by ryanpeden   2021-12-10

I've been here a few times in my decade-long career.

To start, this isn't something that only happens to junior developers. Trying to approach a large existing code base can be a real challenge, even when you have lots of experience.

You're not going to understand the whole application in a day. Probably not even in a week, and probably not even in a month. On some large code bases, I've regularly run into new code *years* after I first started working on the application.

What I've found helpful is to pick a small part of the application; preferably one that's related to a feature you're trying to add or a bug you're trying to fix. Find what looks like the entry point of that small part of the application. In a web app, it could be a method in a controller class. Or it could be a method in a service class somewhere.

Once you've found that entry point, read through the code one line at a time, and try to make sure you understand what's happening at each point. If the method you're in calls another method/function, jump to that and go through it one line at a time. On code that's particularly complex, I'll grab some sheets of lined paper, and devote one sheet to each method I go through.

As I go through each method, I'll write out the whole thing by hand as pseudocode. In order to do this, I have to understand what the code is doing. Some people might find it more effective to do this in a text editor. I find that there's something about the process of physically writing it out on paper that really helps cement my understanding.

Now, the whole writing out part isn't worth it if you just need to go in and do a quick bug fix. But if you've been handed responsibility for a chunk of code and you'll need to understand it deeply, I've found it to be a useful approach. I think it can still be helpful even if you're not solely responsible for a piece of code, but will have to work on it heavily.

Start by deeply understanding one important part of the code. Then move on to understanding another important part. Soon, you'll start to see patterns and understand how these important bits of code fit together.

If you're not yet sure what the important parts of the code for you to understand are, then a good way to find out would be to look at the repository's commit history to see which files have the most commits over time. The places that change the most often are likely the ones *you* are going to have to change, so they are a good place to begin. You can find instructions on how to do this here:

https://stackoverflow.com/questions/5669621/git-find-out-which-files-have-had-the-most-commits

That assuming your code is in a Git repository. If you team uses Mercurial, you can look up instructions on how to do the same thing. If your team uses Subversion or heck, even CVS, you can probably accomplish the same thing. If your team doesn't use source control at all, then start spiking your morning coffee with rum or Kahlua because that will make your job significantly less painful.

For a look at using Git commit history to find the most important code - and the parts with the most technical debt - I enjoyed a book called Software Design X-Rays.

I've found the book Working Effectively with Legacy Code to be quite helpful in showing me different ways to approach an existing code base. Even if you don't apply all of the techniques the book suggests, I think it's still useful for finding out ways to find 'seams' in the code that you can use as points of attack when refactoring, adding features, or even just choosing a place to start learning a new bit of code.

If your employer will let you expense the cost of eBooks, you might find these interesting. If you can get access to Safari Books Online, both these books are available on there, along with a metric ton of great software development books. You might not need to pay for it - in my city, everyone with a public library account can access Safari for free. Maybe it's similar where you are?

Also, if you have a particularly frustrating day, feel free to come on Reddit and send me a DM. I might just have some useful advice. And if I don't happen to have useful advice on a particularly topic, I'll at least be able to come up with an on-topic smartass remark that will help you laugh and feel better about the code that frustrated you.

by rmb177   2021-03-29
Working Effectively with Legacy Code by Michael Feathers is a good resource for how to introduce testing code into an existing system:

https://www.amazon.com/Working-Effectively-Legacy-Michael-Fe...

by bdavisx   2020-09-24
If you're looking for help fixing the mess you are dealing with, find the book Working Effectively with Legacy Code - https://www.amazon.com/Working-Effectively-Legacy-Michael-Fe....

You might also want to read Refactoring to Patterns, but the legacy book is more important to start with.

by xadoc   2020-07-14
If the code has tests, I would start by looking at those tests.

If it has no tests, then I would slowly try to build tests to document the functionality that I need. In your case being Angular that might be having simple html pages with the smallest module that you need.

How to find things? If you're on Windows try AstroGrep https://www.amazon.co.uk/Working-Effectively-Legacy-Michael-...

Lastly, I would raise this because the company might not be aware they are buying a low quality framework that maybe ticks all the boxes in the contract but is in effect impossible to use by their current developers (you), it might be there's other people with more experience in said niche that might be able to help. In the private community maybe some people would be able to accept a short contract to help train you.

by Scarface74   2019-07-21

Yes. But it’s a great paying skill. There are techniques for working with legacy code.

This is the go to book for it. https://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052

by mad0314   2019-07-21

I think it would be a good exercise to learn something new and then go back to your old projects and apply them. For example, learn about testing and go back to your calculator and try to get a good chunk of it under tests (another good resource is Working Effectively with Legacy Code, which covers getting projects under tests when they don't have any). Then learn about CI/CD, which use tests, and make a build pipeline for your calculator. Then read Clean Code, read about architectures and patterns, and see if you can improve your architecture. This is just an example, but any of those could give you good talking points if interviewers ask about your projects.

by denialerror   2019-07-21

Michael C. Feathers (who wrote the book Working Effectively With Legacy Code) defines legacy code as code without tests. If code is tested, you can modify or replace it with confidence and without tests, you can have no confidence that changes to the code won't break something. If you can't change your code without fear of it breaking, your code is legacy.

by anonymous   2019-07-21

What you should have initially are integration tests. These will test that the functions perform as expected and you could hit the actual database for this.

Once you have that savety net you could start refactoring the code to be more maintainable and introducing unit tests.

As mentioned by serbrech Workign Effectively with Legacy code will help you to no end, I would strongly advise reading it even for greenfield projects.

http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052

The main question I would ask is how often does the code change? If it is infrequent is it really worth the effort trying to introduce unit tests, if it is changed frequently then I would definatly consider cleaning it up a bit.

by anonymous   2019-07-21

As far as I know, there are no tools for automatically bringing existing code under unit tests - if it were that easy, there should be no new bugs at all, right? As arne says in his answer, if code was not designed to be tested in the first place, it usually has to be changed to be testable.

The best you can do in my opinion is to learn some techniques of how to introduce unit tests with relatively few changes (so that you can introduce the unit tests before you start the "real" modifications); one book on this subject I've read recently is Michael Feathers' "Working Effectively with Legacy Code" (Amazon Link: http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052). Although it has some shortcomings, it has pretty detailed descriptions of techniques how you can easily introduce unit tests.

by anonymous   2019-07-21

I've had this problem before and I've asked around (before the days of stack overflow) and this book has always been recommended to me. http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052

by mattwynne   2019-07-21

This question came up recently on the RSpec mailing list, and the advice we generally gave was:

  • Don't bother trying to retro-fit specs to existing, working, code unless you're going to change it - it's exhausting and, unless the code needs to be changed, rather pointless.
  • Start writing specs for any changes you make from now on. Bug fixes are an especially good opportunity for this.
  • Try to train yourself into the discipline that before you touch the code, first of all write a failing example (=spec) to drive out the change.

You may find that the design of code which wasn't driven out by code examples or unit tests makes it awkward to write tests or specs for. This is perhaps what your friend was alluding to. You will almost certainly need to learn a few key refactoring techniques to break up dependencies so that you can exercise each class in isolation from your specs. Michael Feathers' excellent book, Working Effectively With Legacy Code has some great material to help you learn this delicate skill.

I'd also encourage you to use the built-in spec:rcov rake task to generate code coverage stats. It's extremely rewarding to watch these numbers go up as you start to get your codebase under test.

by anonymous   2019-07-21

This is often referred to an 'sensing' within a class under test: you have to find a way to tell whether a method has been called. These kinds of things are covered well in the book 'Working Effectively with Legacy Code' which everyone should have (disclaimer: I have no affiliation with the book's author, I just think it's a good book to have).

What does the CreateRegisteringMailMessage method do? Does it affect the content of the RegisterModel instance passed in during the call?

by anonymous   2019-07-21

Answer to most of your questions http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052

by kbouck   2019-07-12
I've heard the book Working with Legacy Code [0] recommended for strategies to bring order to these kind of projects. Haven't read it myself yet...

[0]: https://www.amazon.com/Working-Effectively-Legacy-Michael-Fe...

by edem   2019-07-12
I'd suggest the book [Working Effectively with Legacy Code](https://www.amazon.com/Working-Effectively-Legacy-Michael-Fe...).
by jrochkind1   2019-07-12
This book is pretty great:

https://www.amazon.com/Working-Effectively-Legacy-Michael-Fe...

by kolinko   2019-05-10
Yes. By the way - „Working effectively with legacy code”.

https://www.amazon.com/Working-Effectively-Legacy-Michael-Fe...

If you start from scratch you may bump into the same edge cases that the original writers bumped into, and end up with a code that is not much better than the original - even in the original is 2 years out of date.

I’m sure there were cases when writing from scratch was a good call, but I don’t remember hearing about it.

by anonymous   2019-01-13

I believe that the mocking support in CppUTest is invasive, i.e. you need to add mocking support to your production code as well. Example:

void success(void)
{
    mock().actualCall("success");
    ....
}

For non-invasive unit testing of plain C code you could instead use e.g. preprocessor or link seams. Check out Michael Feathers' great book Working Effectively with Legacy Code for details.

Actually, an extract from that book covering those seam types can be found here. I'd still recommend any C programmer to read that book - it's invaluable.