polycratia

I inherited a Python 2 system, and the migration plan was already written when I got there. Six weeks, module by module, tests green at the end of each one.

What the plan did not account for: the system had been in production long enough to accumulate behaviour nobody had written down.

A rounding convention that existed only because one integration expected it. A date-parsing path that quietly accepted two formats, because an upstream partner changed theirs years earlier and nobody fixed the sender. A background job that leaned on the order rows came back in, since that ordering happened to be stable under the old driver.

None of that lived in the tests. The tests covered the code as the authors understood it. The undocumented behaviour was load-bearing anyway: real integrations had been built on top of it.

So the migration kept passing CI and kept producing small wrong results in production. Not crashes. Numbers that looked plausible and were off.

What I do differently now: before any rewrite, I spend time establishing what the current system actually does, not what it was designed to do. Capture real inputs and real outputs from production, replay them against the new code, and diff. Not assert. Diff. What matters is the differences you did not predict, and you cannot write an assertion for a behaviour you do not know exists.

That changes which questions are worth asking before a rewrite. Not "is the architecture wrong": it usually is, that is why you are here. The useful ones are narrower.

What does this system do that is not in any test or document?

Which of those behaviours does an external integration depend on?

Can I observe current behaviour well enough to compare against?

What is the smallest piece I can replace while both versions run?

And: if the rewrite is wrong, how do I find out, from an alert or from a user?

That last one decides whether a rewrite is survivable. A rewrite that fails loudly costs you a bad week. One that fails quietly against money or documents is something you find out about months later, in data you now have to reconstruct.

Monolith-to-services work has the same shape, just distributed. The seam you cut is the seam where the undocumented coupling lived.

I wrote more about how I approach inherited systems and migration seams on my blog: https://polycratia.com/c/cf819e95

For those of you who have done a large migration: what caught you that was not in any test?

react

$ new-project --brief

or email hey@polycratia.com