DEV Community

Bertil Muth
Bertil Muth

Posted on

What's your strategy for refactoring legacy code?

Top comments (3)

Collapse
 
cjbrooks12 profile image
Casey Brooks

The hardest part of refactoring legacy code is ensuring your changes to don't break something else, and its hard to check that without having lots of tests. Unless (of even if!) you wrote the entire codebase, it can be tricky to know the full extent of what-depends-on-what, so you need a strategy to verify this.

I also tend to start by finding the smallest-reaching parts of the program that can safely be cleaned up, and starting there. Once you have the smaller bits cleaned up, it should be easier to determine the reach of some of the bigger chunks. Just keep going up the chain in this fashion.

For example:

  • Start by cleaning up a few of the messier methods in a smaller class, and you might find some shared logic there that could be reused. Only change the internals to those methods, making new private methods as needed, but don't change anything that would affect or be visible to anyone using the class.
  • Once you've cleaned some of the messier methods in that class, try to clean up the entire class or even improve its public-facing API. By now, you've probably got a better idea of what this class is actually doing, and you can improve how its clients interact with it accordingly. This should be backwards-compatible so that using this new API is purely opt-in.
  • After one class has a better API, start going through the codebase and replacing usages of the old API with the new one. In Java, you can add @deprecated to the Javadoc of the old methods and the compiler will warn you when it is being used, which may be helpful.
  • Pick the next class you can do this process with, and keep going.

Another practical way that I like to refactor large, legacy codebases is by attempting to modularize it. Move classes into isolation so that other parts of the program cannot even see it, or have no way to directly interact with it.

This is pretty easy if you're using Java and Gradle, as you can pretty easy just move code into another Gradle subproject and the compiler will tell you exactly what will break. In a lot of cases, what immediately looks like a dependency is actually just spaghetti code and methods that should be moved closer to where they are actually needed, and the whole lot of them can be moved to another module together.

Unfortunately, I do not have much experience with this outside of the Java world, but similar techniques should be able to be applied with care: Identify all parts of the app using a small bit of code, figure out how much of that code can be extracted somewhere else, and clean up the extraneous bits as you make that move. Effective use of namespacing and working to eliminate global state can help here, as it will enforce boundaries in your code that should be maintained.

Collapse
 
bertilmuth profile image
Bertil Muth

Thanks a lot for writing such a detailed answer!

Collapse
 
rhymes profile image
rhymes

At an abstract level: write a test for the functionality, change the code, run all the tests, repeat.