Git and the Mikado method

November 25th, 2021

Git and the Mikado method

It’s no secret that refactoring or bringing any major changes to a big codebase is tricky business.

Typically, one begins with a reasonably well defined goal. The hacking starts. Before long one or two tests start failing. But we’re in the middle of changing a fundamental piece of code, so that’s nothing to worry about.

But wait, we discover that to get to our goal we have to change an API or the way a function behaves. So we get on to that. But that makes more tests break. Well, that sucks, but they’ll be soon be working again as soon as we— oh crap. There’s a third thing that we need to do so that the second thing gets back on track…

And so on, and so forth. The more we progress, the more tests and functionality break down as the overall system becomes a fractured creature, an increasingly dismal patchwork of old and new.

I call this lovely pattern the “Widening Gyre of Despair”. Usually, once a poor soul is caught it in, they have no choice but to either clench their teeth and push through or, and I might have done that a few times, flip all the proverbial tables, and decide that screw it, we’ll rewrite the bloody thing from scratch.

Fortunately, saner strategies exist. One of them is the Mikado method. I like it because its core principle is both simple and sensible. In a nutshell, the development process it advocates is:

  1. Set a goal for your coding adventure. Bonus points if you can give a concrete shape to that goal in the form of tests.
  2. Try to reach that goal via the simplest implementation possible.
  3. If you think of improvements as you code, don’t immediately go chasing those waterfalls! Instead, create tickets so that you can work on those later on, after the initial goal is in a working state.
  4. Now comes the magic step. If you come across a change that you must bring to a subsystem, or anything that can be described as a sub-problem, you don’t plow forward. Instead park all the work you did for that initial goal, return the codebase to the state it was before you began to work (which should be pristine and in working condition), and return to (1), with the sub-problem set as your new goal.
  5. Repeat the whole process, recursing as much as you need to.
  6. When you finally achieve one of the goal, rebase the work of its parent task atop it. Congrats, you knocked out a dependent task without generating telescoping failures.
  7. Continue until you are through the whole recursive stack of tasks. Then savour your flawless victory.

The methodology has more to it, but as far as I’m concerned, that’s the magical bit. Mind you, it’s neat, but it’s not without its price. Doing all those individual steps such that they bring incremental evolution to the codebase is more work than going through a “one sweep” revolution. But there are much less chances to become mired in the Gyre, and each step is a working one and can — in theory — be deployed such that there can be continual progress. Which is a welcome change of the classic “start working, nothing to show for it, still nothing, well it’s all broken now, still all broken, oh god so much brokenness, ok I might have it, I think I do, DEPLOY ALL THE THINGS”.

This methodology also works admirably well with git. Indeed, stashing ongoing work? Rebasing one branch of work over another? That’s nothing but git’s very own bread and butter.

While it’d be totally cromulent to follow this development strategy with basic git commands, I had this notion that it could benefit of a helping tool. A little like the git subcommands available for Gitflow.

So put on your surprise faces, and say “hello” to my new little friend: git-mikado.

Installing git-mikado

First, a disclaimer: git-mikado is alpha quality and mostly for my own consumption. But if you want to join the fun, you can install it on your computer by doing:

$ npm install -g

(or if you don’t trust weird urls, go to the project’s repository and clone it.)

Using git-mikado

Considering any documentation has yet to be written, the best way to show what git-mikado has to offer so far is to go though an example. So, let’s say we’re working on my infamous Spaceship Game. The code in the main branch is currently in a stable state — all tests pass, nothing’s on fire. Feeling bold, we decide on our next big feature: the implementation of the ships’ command panel.

In addition of creating a new command-panel branch, git mikado new assigns it two pieces of data: the name of the “sane, all in working order” branch it’s cut from, and the name of the branch it will be merged to once it’s done. In this case, both are main.

We begin hacking, and soon realize that the command panel is actually made of three sub-components: the display of the ship’s structure, its navigation systems, and its ever-important weaponry.

Okay, let’s create a new ship-structure branch.

Oops! Forgot to commit a thing or two. git-mikado is very easy-going and generally unopinionated. But there is one thing it’s adamant about, and it’s that everything is stashed or commited before it does anything.

Fine, we commit our in-progress work, and try again.

This time around, git-mikado is in a position to make more assumptions: the current branch becomes the upstream of ship-structure and we inherit its base branch (if it was good enough for the upstream branch, it’s good enough for the new one).

With that done, we can now do git mikado status to get an idea of what the big picture looks like.

The status report has two sections. The first one lists all the mikado’ed branches, with their base and upstream branches, while the second one gives us all the things we can act on. In this case, we can only work on the ship-structure branch as all the other branches have non-completed dependencies.

Before we begin to work on this branch, let’s create the two other big features that we know we’ll have to do.

Our status begins to looks more fleshed out.

We can now begin to work on the ship structure. And we immediately realize that the structure component is itself composed of the display of the hull and of the armor. Okay. We git mikado new them and we end up with a few more branches still.

Now deep into those recursive dependencies — but not lost as the What’s next? section keeps us grounded on the unblocked leaf tasks — we check out the hull branch and begin to code. Turns out there are no further sub-division to be made and after some happy typing, we are done. We commit, and flag the branch as done:

git-mikado helpfully tells us where that branch should be merged. Information that is also reflected in the status:

Perfect. Time to bring the changes to the upstream. git-mikado purposefully keeps out how the details on how that happens. Merge? Rebase? It doesn’t matter, as long as the code makes its way to the upstream, all is good. Since I am in the driver seat, we’ll follow my usual procedure: we merge hull into main, and rebase ship-structure on top of it.

After we’ve done so, the status now looks like:

Progress, and new stuff on the todo list! Since main got more commits, the branches we cut from it are now trailing behind and need to be rebased. Well, technically they don’t need absolutely require to be rebased, but that how I like it. Beside that, since the branch hull is now part of ship-structure, it can be safely deleted.

And so we delete hull and rely on git mikado status and the what’s next? section to drive our next steps. We’ll add new sub-divisions of our branches via git mikado new when we stumble upon them, but otherwise trudge along until we are done.

Is that it?

It is! Mostly.

There are a few flags and options I didn’t cover in the example. There is also an additional command I didn’t mention — git mikado upstream — that allows you to assign more than one upstream to a branch (in my projects I typically add the main branch in addition to the original upstream).

As I said, git-mikado is still very alpha. I still want to add some current command to flag the branch that is currently worked upon. I also want to refine the way the status is being shown, with colors and styles to reflect the state of the different branches. And dependency graphs. And…

But the core idea is all there. Between the new and status commands, we have what boils down to a minimalistic task dependency management with a dashboard taking the mental pressure of having to figure out what we should and can do next. Which, if you have a scattershot brain like mine, is a pretty helpful thing indeed.