Smooth code refactors using the Mikado Method
Have you ever started to refactor some legacy code and found out after some hours you have edited more than twice the files you expected at the beginning? Maybe you wanted to make a commit earlier but you weren’t able to get your code compiling. If you have ever faced this situation this article may help you next time you’re refactoring.
Developing new features on a legacy codebase can be tricky. It is easy to become overwhelmed by a mound of class dependencies and untested code, whose functionality needs to be preserved but also adapted to fit new demands.
A month ago, I found myself on a team confronting this exact challenge. This blog post forms a quick overview of a technique we found useful to avoid huge commits and broken pipelines while progressing in the refactor with every commit we made.
The goal of our task was to create a new dashboard screen in the app. This feature was going to go live only in a few markets so we needed to maintain the old dashboard until the new one got adopted by all markets.
Based on that, we decided to create a new feature module for the new dashboard. We also decided that the new feature module was not going to have any dependencies with the old module. This forced us to create an additional base module to share the code and dependencies needed by the two feature modules. Because of this, we needed to move the needed dependencies from the old feature module to the new base shared module.
Ideally, after finishing the refactor the old module would contain code needed only by the old dashboard. This means after the new dashboard got released in all markets the old module could be safely removed.
Our first approach
Once we had the new feature module ready we started to create the Activity, Fragments, ViewModels, etc needed to handle the new feature in our new module. At this point we realised we needed to pull a few classes from the old dashboard module to reuse some existing behaviour: interactors, repositories, mappers…
We picked our first victim (the one in red in the diagram) and dragged it into the new module. And that was it, we had our first class refactored and we were able to use it in the new dashboard.
But as one of my colleagues says “Life if not a picnic” and reality started to hit us in the face. After moving this class to the new module the project was not compiling anymore. We realised this class had some dependencies to other classes from the old module so we proceeded to pull those classes as well.
If you recall what we defined at the beginning, the classes in the new feature module can’t have dependencies with the old one. A few hours after starting the refactor we realised we had been pulling classes following the dependencies graph and the project was still very far from compiling. We found ourselves sinking into quicksand: whenever we fixed 1 problem, 2 more arose.
The Mikado Method
For small changes you can keep things in your head, but for larger ones the chances of getting lost in a jungle of dependencies or in a sea of broken code increases dramatically. The Mikado Method is the map to guide you in restructurings that take days or weeks, helping you divide almost any problem into small, conquerable piece-meals.
There are four basic and well-known concepts that summarize the “process” of the Mikado Method:
- Set a goal: think about what you want to achieve. (E.g. share an interactor between the two dashboards). The goal serves two purposes: firstly represents a starting point for the change and secondly determines if the method has achieved success or not.
- Experiments: an experiment is a procedure that makes a discovery or establishes the validity of a hypothesis, i.e. the changes you apply to the code. E.g. move a method from one class to another, extract a class or reduce the scope of a variable…)
- Visualization: writing down the goal and the prerequisites to that goal.
- Undo: when an experiment for implementing a goal or a prerequisite has broken your system and you have visualized what you need to change in the system to avoid it breaking, you restore the changes you made to a previously working state.
The Mikado method proposes a simple solution. For each change, when you find the dependencies that show errors once you make that change, you create a graph that depicts these errors, along with what needs to be done to fix them before actually making the change. Then you revert your change and start looking at one leaf in that graph. Fix that error, see if that causes more problems — if it does, repeat the process — continue drawing more leaves on the graph with details of what else needs to be changed, revert the entire code change, and start working on the leaf again.
At each point when you revert the code, you might feel that you are back at square one, but you are not — you actually have more information than when you started with. Also, you are always working with code that compiles (and passes tests!), rather than having a lot of code that is not compiling, so that makes it possible to use the IDE refactoring tools.
Each time a leaf node problem is fixed and it doesn’t lead to more errors, the state can be checked in very easily and the leaf can be marked green; once all leaves from a node are green, you can start working on that node and so on until you finish the original change.
Our second approach
Following the steps defined by the Mikado Method we set our goals and moved the classes in the same way we did in the first approach. But this time using the Mikado Graph we identified the most internal items (the leaf items shown in green in the image below). After undoing the changes we started the refactor moving those classes to the new package.
After applying some iterations we were able to refactor all the classes that had dependencies with our new code. In addition, with each iteration we were able to do small, simple and clear commits while we kept our tests updated and passing all the time.
Why is it called "Mikado"
It’s a reference to the Mikado pick-up sticks game.
It’s tangled with dozens of other sticks: annoying dependencies and tweaks you need to make, so the code still works. The strategy is to remove the easy sticks first. The ones that are not tangled. Progressively, you untangle your Stick. Until you can reach it without breaking anything 🎉
There’s even a book that goes deep in detailing this process: The Mikado Method.
With a bit of practice, you’ll find yourself good at it and you’ll become a much more efficient developer!