The situation
A client had a twelve-year-old batch job. One file, no tests, a cron entry someone had added years before anyone on the current team arrived. It ran every night and never asked for attention. Read as code, it was everything a modern engineer is trained to dislike: no separation of concerns, no events, nothing to observe.
So the recommendation was to replace it with something clean. Queues, retries, dashboards, an event-driven design that was better on paper in every direction. We made that case, the client agreed, and we built it. This is the honest counterweight to most of what we write, because the advice that follows is advice we had to pay to learn.
What was really going on
The old job was ugly, but ugly is not the same as wrong. That single file had quietly encoded a decade of edge cases: the customer with the strange billing cycle, the holiday schedule that broke the obvious logic, the upstream system that still sends malformed dates. None of it was written down. It lived only in the code, accumulated one incident at a time over twelve years.
The rewrite did not inherit any of that, because there was no specification to inherit it from. We rediscovered each edge case the hard way, by breaking it in front of the client and then working out why the old job had handled it. The clean new design also had more moving parts to fail. A 2am page now needed three people who understood the event flow, where the old cron line could be read by any junior on the team.
What we did
We spent four months and roughly 70,000 euros chasing parity with a script nobody had documented. That figure is openly estimated rather than audited, but it is the honest order of magnitude, and it bought nothing the business did not already have. The strangler fig pattern that would have told us to do this incrementally has been named for two decades. We knew the theory cold and billed the client anyway, because we trusted the architecture diagram over the working cron line.
So we changed the question we bring to old systems. Not “is this modern” but “which parts actively hurt, and which parts quietly pay rent”. You strangle the parts that hurt. You leave the parts that work alone, however unfashionable they look. Most of the time that is a smaller, cheaper, less heroic project than the rebuild, and it does not put a working business at risk to settle an argument about architecture.
This is the discipline behind our architecture and technical leadership work: advising against the rewrite when the rewrite is the wrong call, even when a rebuild is the easier thing to sell. We wrote about the narrow set of conditions that do justify a rewrite in when a rewrite is the right call.
What changed
The client got a working event-driven system in the end, at a cost that a targeted refactor would have avoided entirely. What we got was a rule we now apply before recommending any rewrite: legacy code is usually not a failure. It is something that worked long enough to matter, carrying decisions you can no longer see.
The enemy was never the old system. It was the assumption that new meant better. That assumption cost the client 70,000 euros before it cost us the belief, and it is why we now argue candidates out of rewrites as often as into them.
If you are weighing a rewrite and the main argument for it is that the current system is old or ugly, that is worth pressure-testing before the budget is committed. Talk to us first, and we will tell you honestly if the smaller project is the right one.