2025 Public Training Schedule
January 14 – 17, 2025 – Agile Analysis and Design Patterns – Half-Day Sessions
(c) 2024 To Be Agile
The so-called safe refactorings are a subset of the refactorings from Martin Fowler’s book, Refactoring: Changing the Design of Existing Code. We call them safe because they require only straightforward changes to code that can be automated and proven to be correct with no unintentional side-effects.
Safe refactorings are refactorings like Rename Method that lets you change the name of a method or Extract Method where you can take a large method and extract out smaller methods from it. Conversely, you can take small methods and inline them to make larger methods. Each refactoring typically has an opposite refactoring, for example, extracting or inlining methods.
Some refactorings, however, are more complex so there are more opportunities for something to go wrong. Before attempting to do major changes to existing code we often would like to have that code under test with good automated regression tests so that if we make a mistake or change the behavior of the system as we are refactoring it then our tests will tell us and we can back out right away.
But when we have legacy code that is not under test it becomes dangerous to do complex refactorings and so the strategy that we typically take is to do safe refactorings to give us the opportunity to inject dependencies, which lets us write betters tests so that we can then do more complex and riskier refactorings having the code now under test.
Often times, the process of getting legacy code under test is a process of creating what Michael Feathers talks about in his book, Working Effectively with Legacy Code, as creating seams. A seam is an insertion point where you can inject a dependency and this is typically one of the things that allow us to make code more testable.
Therefore, much of the process of refactoring and working with legacy code is a process of first changing the relationship to dependencies. Instead of directly creating and calling dependencies in our code, we create seams in legacy code that allow us to inject those dependencies and then, in our tests, we use the seams to inject fake dependencies that allow us to test our code’s interaction with those dependencies without having to have those dependencies present.
This can be a slow and involved process but once we have good unit tests for the behaviors of an existing system, it’s like having a safety net that allows us to make changes to the system knowing that if we make a mistake and mess up then our tests will tell us and we can immediately recover.
In the process of retrofitting tests into legacy code, we are often doing many other things. We are also often rewriting the code, making it more straightforward to understand and work with in the future. When legacy code is difficult to test it will need to be rewritten or refactored in order to make it more testable which often has the benefit of making the code more extensible to adding new features.
If you need to refactor or extend the behavior of an existing system that is not under test then often the best first step is to retrofit tests into the system by adding seams, because having tests allows us to safely do more risky refactorings to improve the design even further. And that, in a nutshell, is how we improve legacy code.
Note: This blog post is based on one of the “Seven Strategies…” sections in my book, Beyond Legacy Code: Nine Practices to Extend the Life (and Value) of Your Software.
Previous Post: « Refactor to Make Small Improvements
Next Post: Refactor to Clean Up as You Go »