advanced 3 min answer

Replacing a home-grown money type with a decimal-backed one fails to compile in 180 places across 11 modules the moment you try it. The team's previous attempt lived on a branch for five weeks and was abandoned. Sequence this so it lands and stays landed.

refactoringmikado methodtrunkincremental changerounding
Show the full answer Hide the answer

The sequence

  1. Probe, do not fix. Make the naive change and compile. Write down every break as a prerequisite - "module X needs its own currency rounding", "report Y depends on the integer cents field". Then revert to green. The probe was for information, and the information is the list.
  2. Draw the graph. Prerequisites hang off the goal, and prerequisites of prerequisites hang off those. The 180 compile errors are not 180 units of work: they collapse into a handful of causes, typically a dozen or so leaves. This is the Mikado Method, named and written up by Ellnestam and Brolund (Manning, 2014), and the graph is the artefact that makes the shape visible before you commit to a branch.
  3. Work the leaves, on trunk. A leaf is a prerequisite with nothing underneath it, which means it can be done alone, tested, reviewed and merged today without the goal being reached. Each leaf is a commit that is releasable by itself.
  4. Introduce the new type beside the old one, with explicit conversions at the seam, rather than swapping them. Both coexist; modules move one at a time. This is branch by abstraction applied to a type rather than a component.
  5. Migrate module by module, highest-value or lowest-risk first depending on whether you need confidence or momentum.
  6. Delete the old type and the conversions. Only now is the goal reached.

Why the revert matters

It is the step teams skip, and skipping it is what produced the five-week branch. If you keep the half-finished probe, you have started the big-bang change under a different name: the branch is now the only place the work exists, every leaf you fix is entangled with every other, and the diff grows faster than the understanding does.

Reverting costs you nothing you need. The graph is the output. A probe that lasts an hour and produces twelve leaves is a better day's work than a thousand-line branch that compiles.

Where it can diverge and how you would know

Money changes are not type changes; they are behaviour changes. A decimal type rounds differently from integer cents in division, percentage and allocation, and nothing in the compiler notices.

  • Run both implementations in parallel over real inputs - the old path serves, the new path computes, and the two are compared and logged. Any difference is a finding, including one of a hundredth of a unit.
  • Compare over a full accounting period, not a day, because the differences cluster in month-end allocation, tax splitting and refund arithmetic.
  • Reconcile against the ledger, not against a unit test. The failure you are looking for is not an exception; it is a total that is 0.01 out across 40000 rows, which arrives as a finance query weeks later.

The point of no return and the rollback

Every leaf commit reverts on its own, so the sequence is reversible until two specific moments: the first write of the new representation to storage, and the deletion of the old type. Keep the conversion seam in place until the parallel comparison has been clean for a full period, and make the write path dual-format until then. After the delete, rollback means restoring code and re-reading data written in the new shape.

Realistic elapsed time: the leaves land in days, the module migration in weeks, and the comparison window is set by the accounting calendar rather than by engineering - plan for a quarter, most of it waiting.

When this is the wrong approach

When the graph will not stop growing. If after a day of probing it is still widening and more than three or four levels deep, the change is a rewrite wearing a refactor's clothes. Two honest responses: cut the scope - migrate one module and keep the conversion seam permanently - or fund it as a project with a parallel-run plan, which is a different conversation with different approvals.

And for a change that genuinely fits one sitting - 180 call sites of pure mechanical rename, no behaviour change - the graph is overhead. Write the codemod, land it in one commit, and spend the saved time on the test that proves nothing moved.