Paul Hammant's Blog: Java legacy hairball: refactoring case-study
Ten years ago, I led a team for a banking client to rejuvenate their FX-trading platform. One aspect of that mission was to snip away at the existing singleton-laden solution in order to make a cleaner and more unit-testable. I’ve written the case-study for this methodical (and pauseable) decomposition project over a few prior articles:
- March 20th, 2013 » A Singleton Escape Plan
- March 11th, 2013 » Legacy App Rejuvenation
- April 01st, 2008 » Drinking your Guice too quickly?
- April 26th, 2007 » Introducing Branch By Abstraction
From the 2008 article - that introduced Most Depended-on and least depending component as a strategy - an attempt to find a visual metaphor for entanglement/detanglement:
Today though, a slide deck from back then used to convince the client to start the rejuvenation work. This time redone as SVG (was previously Keynote/PDF) and now in-lined into this article.
Note: for the sake of this article ‘singleton’ is the Gang of Four pattern, not the Spring/Guice idiom.
Eleven slides showing the first refactoring
If you can’t see any pics below, then your browser isn’t showing SVG inline. Go get Google-Chrome, Firefox or Safari, friend.
Architecture as described by the client:
The Starting Point was the logical business tier:
It turns out, it was not as decomposed as hoped for:
Make room in the diagram for more boxes:
Introduce Service Locator and a place for refactored components:
Identify the **most depended on, and least depending**, component:
Move it to new codebase (make sure its unit tests come too and rejuvenate those):
In old codebase, two depending components now look-up the new component via the Service Locator. Part new and part old, the app/service should go live at this point:
Identify another component to move (**most depended-on and least depending**):
Move it as before. This time with a pre-existing dependency (via Service Locator):
In the old codebase, add one more looks-up to the new component (via the Service Locator). Repeat the last three steps until there are no singletons left:
A methodical nature to this
The refactoring effort could pause at any stage (as there are higher priorities for the dev team), or carry on methodically with other business-priorities. Indeed the app/service going live at a certain cadence, perhaps should be part new functionality and part refactoring. The get there, you really want to be doing Trunk-Based Development, and perhaps even Continuous Delivery. If this work is being done on a separate branch, with the promise of a merge back later (and a go live), there is an increasing risk of the business halting the initiative, perhaps irrevocably with career impacting consequences.
The project back in 2006? - We were concurrently migrating from multi-branch ClearCase to single Trunk Perforce. Components were being shaped up first in ClearCase, and removed at they were being put the Perforce trunk in a hierarchical source tree and build (Maven style, but in Ant). Things from ClearCase that needed components now in Perforce found them in ‘binary repo’ (WebDav then, but like Sonotype’s Nexus or Artifactory). Gradually components (and their tests) migrated out of ClearCase and into the Perforce Trunk with a hierarchical build. CruiseControl (a precursor to Jenkins, etc) had a build that would create the binary artifacts in the repo, as well as use them in builds. That CI pipeline would be triggered by commits to one of two SCMs and well as binaries being replaced in the binary repo.
All the while, teams that were not focussing an iteration on the migration would carry on with business functionality. The sum of the business functionality and the refactoring was put live in the same release, on an unchanged (or bettered) cadence. The business never knew the refactoring was going on, or that test coverage was increasing.
The guiding principal was to attack the “Most Depended-on and least depending component” only. Each time that was achieved, there’d be a new candidate for that in the hairball.
A couple of years later, at Google, I worked with Angular-creator Misko Hevery’s intern (David Rubel) on the Google Singleton Detector. This helps easily visually identify most Depended-on and least depending component for future initiatives. Helped, perhaps, it might not work with Java 8/9 anymore.
After all the singletons are gone, what next?
Go to dependency injection, of course. You would do this in a second series of refactorings and stop when there nothing using the service locator any more. The latter has to go still, as it facilitates undesirable static mutable state.
Danilo Sato shows you how with Intellij IDEA, in this super impressive video:
Ref: Refactoring Experiment on Vimeo.
Danilo does one refactoring at a time pushing service-locator lookups up to the constructor, then into the constructor’s parameters, and finally towards the startup class that composes the solution.
Danilo’s blog entry that accompanies the video: Refactoring Strategies: a walkthrough experiment.
Stupidly homegrown and detailed here.