When developing new features at Vinta, we embrace Test-driven development (TDD) for its iterative and confidence-building approach. However, when it comes to refactoring existing, complex features, the challenge is different. Analyzing dependencies in a top-down manner can lead to analysis paralysis, while a bottom-up approach may result in frustration.
Enter the Mikado Method, as introduced in the book by Ola Ellnestam and Daniel Brolund. This pragmatic approach allows for gradual and secure execution of intricate refactorings, offering a solution akin to TDD's iterative process. The development team at Vinta has successfully adopted this method, providing a structured approach to evolving large and complex features.
Are you curious to learn more about the Mikado Method in practice? Continue reading our blog to discover how this approach transforms how we tackle complex refactoring challenges.
The Mikado Method in Practice
As Test-driven development (TDD) empowers us with new features, the Mikado Method is a pragmatic ally when navigating the intricate landscape of refactoring existing, complex code. This method provides a gradual and secure approach to executing intricate refactorings. Let's delve into how this method transforms how we navigate and conquer the challenges of code evolution.This chart summarizes the Mikado Method. In the following sections, we will explore each step of this chart.
In one of our projects, we had a Django model used to control the execution of a series of daily batch tasks, synchronizing data with an external data source. This model, "DailyProcessingStatus," became redundant as we started splitting its responsibilities across the relevant domains and entities, and we were now ready to retire the usage of this model altogether. We knew this model was referenced in dozens of files and tests, and there could be places where minor tweaks in logic would be required to accommodate that.
Applying the Mikado Method, we defined our primary goal: "Retire DailyProcessingStatus," and established criteria to know when we reached it - searching for "DailyProcessingStatus" in the codebase should have no matches besides the model declaration itself.
Having our Mikado goal, we found a suitable place to start in the code and started a countdown timer set to 10 minutes. When the timer finished, we checked our status: the goal was not reached (unsurprisingly), and we had a few broken tests. In that case, we had to do two things:
- Extend our diagram with what prevented us from reaching the goal. We discovered some prerequisites!
We reverted the changes we did with git reset --hard. We added it to a chart.
It may be surprising and counterintuitive to undo the work done in the last 10 minutes, but this is fundamental for this method to work: we want to ensure that our codebase is always stable (all tests passing) and that we're working on small steps. These 10 minutes of investment yielded no code being committed, but something more important: we learned more about the refactoring we want to do. The first iterations of the Mikado are focused on discovery, not pushing code changes.
After a few rounds of 10 minutes, our map looked like this:
That's when we finally completed one of the prerequisites within 10 minutes, and all unit tests passed! Great!
When this happened, we did the following:
- Committed the changes with git commit -m "XXXXXXXXX". Note that the prerequisite text makes an excellent commit message.
- Marked the prerequisite as done on our chart (painted it green) and went back to its parent (if the prerequisite had a sibling, that's the one we would pick)
So now we know both the success and failure procedures for our 10-minute iterations. While in the first iterations, it was expected not to reach the current Mikado goal and extend the chart, there's a point of inflection in the process where we start finishing several steps in 10-minute chunks, which is very rewarding.
Eventually, all sub-goals were marked complete, and we reached our primary goal. As a final test, we searched the code, and sure enough, the only places where the model was found were on its declaration and related migrations.
Benefits of using the Mikado Method
We have discovered several benefits of using the Mikado method, so we strongly encourage the squads at Vinta to adopt it. One of the key advantages is that it provides clear visibility of our progress. In cases of interruptions or when a task cannot be completed in a single day, we can quickly resume from the latest node on the map we were working on.
Additionally, the Mikado method helps us minimize the need to keep excessive context in our heads. With each iteration lasting only 10 minutes, we can focus on a limited amount of information at a time. This not only aids in handling interruptions effectively but also significantly enhances overall productivity and code quality.
Another benefit is that it encourages making small, atomic commits with meaningful commit messages for each prerequisite completed. The Mikado method also supports the creation of partial pull requests, which can be particularly useful for large refactorings. By ensuring that all commits have passed tests, we can send smaller pull requests. In some cases, we can even include the Mikado diagram to provide a comprehensive overview of where these pull requests fit into the overall plan.
Furthermore, the method promotes a flow state by allowing us to focus on solving one small problem at a time. This approach is similar to the benefits of Test-Driven Development (TDD), where individuals can concentrate solely on the next task without being overwhelmed by the overall problem.
Revisiting Time Slots and Reverting Changes: Efficiency in the Mikado Method
What about the time slots? And reverting my changes? Isn't that inefficient?
In the brilliant article "TDD is Kanban for Code," Kent Beck wrote:
"The goal of kanban is to increase the value of a production process. Kanban increases the feedback in production by limiting the work in progress at any one time. Without the “safety” of inventory, troublesome process steps and connections between steps have nowhere to hide. With better feedback, the steps can be optimized (or eliminated), and their connections improved. The result is higher throughput and lower latency and variance."
Kanban achieves this by limiting work in progress and exposing troublesome process steps and connections. Beck draws parallels to Test-Driven Development (TDD), where each test written represents a unit of work focused on a specific goal.
The same principle applies to the Mikado Method. Working in small, fixed time slots and reverting changes when the goal is not met is the core of Mikado, ensuring that the work in progress (WIP) aligns with the ideal value of 1 for a single person, which maximizes throughput and keeps the process sane.
While it's initially tempting to ignore the timer and continue coding, doing so often leads to issues similar to unstructured refactoring: numerous broken tests and many changed files (large WIP). Ultimately, these changes are reverted to stabilize tests again. The crucial takeaway is that, unlike writing a new feature, the most challenging aspect of application-wide refactorings is not writing or moving code but understanding the big picture and finding connections and interdependencies.
At Vinta, we recognize the intricacies of application-wide refactorings, and we've adopted the Mikado Method as a potent tool in our software development process. Our approach empowers organizations to confidently tackle large-scale changes, ensuring stability and maintaining codebase quality.
Do you have questions or seek further insights into our software development services? Feel free to reach out to Vinta. Our dedicated team is ready to assist you and deliver tailored solutions to your unique software development needs.