Sep 5, 2026 · rewrite-or-refactor · robot-software · team-decisions · ros2
When to Rewrite Robot Software and When to Live With It
Live with the code and replace one layer at a time, because a full rewrite of a working robot almost never fixes the complaint that started the argument.
Live with it and replace one layer at a time, because a full rewrite of a working robot rarely fixes the complaint that started it. The complaint is a boundary rather than a language or a framework, so swapping ROS 2 for a middleware such as HORUS changes nothing unless the boundary is where the pain is. That flips when the assumption you are fighting is baked into the stack itself. The rest of this post is for a team that has said the word rewrite out loud and now has to decide what to do about it.
It usually surfaces in a retro. Somebody says the thing everyone has been thinking, which is that this would be quicker if we just started again, and half the room agrees immediately and the other half goes quiet, because the quiet half has done it before.
The evidence gets recited in the same order every time. There is a file nobody wants to open, and it is the file that runs on the robot. There is a module still named after a feature that was cancelled two years ago. The startup sequence has an ordering requirement that one person discovered and nobody has documented. A bug fixed in spring came back in autumn somewhere else. Onboarding a new engineer takes weeks, and most of those weeks are spent learning things that are true only here.
And there is the branch. Somebody has a prototype of the replacement, written on evenings, that does the demo in a fraction of the code and has never touched a real robot on a bad day.
Should you rewrite your robot software or live with it?
Live with it, and replace exactly one layer, chosen because it keeps failing rather than because it embarrasses you. A working robot contains an enormous amount of undocumented knowledge, and almost none of it is in the design. It is in the ordering, the retries, the timeout somebody tuned on a Friday, the sensor reading that gets discarded on startup for a reason lost to history. A rewrite discards all of that and rediscovers it in the field, in front of customers.
The exception is real and worth naming. If the thing blocking you is an assumption built into the foundation, so that every fix means fighting the same design again, then no amount of tidying reaches it. Replace that foundation, not the whole house.
The way to tell the difference is mechanical rather than emotional. Fix one representative bug and watch where the change lands. If the fix is local, you have ugly code, which is survivable. If the fix requires edits in four places that must agree, you have a design problem, and design problems are the only ones that justify replacing anything.
What counts as a rewrite in robot software?
A rewrite means throwing away working behaviour and writing it again, and teams routinely use the word for three cheaper things that carry none of the same risk. A refactor changes the shape of code while behaviour stays identical, verified by the robot doing the same thing afterwards. A port moves existing logic to a different framework or language with the logic intact. A replatform changes what runs underneath while your own code stays roughly where it is.
Only the fourth thing, writing the behaviour again from a blank file, is a rewrite in the sense that ends careers. It is the one that produces a six-month period where the old system is not improving and the new one is not shipping, and it is the one where a competitor's release date is decided by your internal calendar.
The distinction matters because the word chosen in the meeting decides the budget. Say rewrite and the discussion becomes a strategic argument nobody can settle with evidence. Say we are replacing the motion layer and the discussion becomes a scoped piece of work with a date and a way to check it.
What are your actual options short of a full rewrite?
There are about seven, and the useful ones sit in the middle where the robot keeps running. At the cheapest end is living with it deliberately, meaning you stop apologising and write down the ordering rules and the known traps instead. Next is a targeted refactor of the single worst module, behaviour unchanged and provable. Then the strangler approach, where one process at a time is replaced behind an unchanged message boundary while the old one keeps running beside it.
Beyond those sit the platform moves. Porting onto ROS 2 buys drivers, recording, replay and a hiring pool, and it is the standard destination for teams whose homegrown stack has run out of tooling. Moving one tightly timed layer onto a focused middleware such as HORUS, where Rust, Python and C++ processes share memory on one machine, suits teams whose complaint is specifically the handoff between their own processes rather than a missing ecosystem. Then there is buying a supported platform, and finally the full rewrite, which is correct roughly as often as teams believe.
How do the options compare when the code is already running?
The comparison turns on what each option costs while the robot still has to work, not on how clean the result would be. Read the final column first, because that is the one teams skip.
| Option | Who it is for | What it assumes you know | When to pick it | When not to |
|---|---|---|---|---|
| Live with it and document | Teams shipping now with a working machine | The undocumented rules your own robot depends on | The complaints are about taste rather than failures | The same class of bug returns every month |
| Targeted refactor | Teams with one module everybody avoids | How to prove behaviour is unchanged before and after | The trouble is concentrated in a small area | The trouble is spread across every boundary |
| Strangler replacement | Teams that cannot pause shipping | Your message boundaries, and how to run old and new together | The robot must keep working throughout | There is no boundary to hide the swap behind |
| Port to ROS 2 | Teams whose homegrown stack lacks tooling | Publish and subscribe, launch files, a Linux workstation | You need drivers, recording, replay and hireable skills | Your ecosystem needs are already met |
| Move the timed layer to HORUS | Teams whose own processes miss each other's deadlines | Rust, Python or C++, and what your loop must not miss | The handoff between your processes is the complaint | You need mapping, planners and a large ecosystem |
| Adopt a supported platform | Companies shipping into regulated or unreachable settings | Procurement and running a vendor relationship | Certification and a phone number matter most | Your budget is people rather than licences |
| Full rewrite | Teams whose foundation blocks every fix | Everything the old system learned the hard way | The old design cannot express what you now sell | The old system still ships and still improves |
No row is the safe default. The right row is the one whose last column does not describe your situation.
Does the size of your team change the rewrite answer?
Yes, and it changes it more than the state of the code does. A rewrite requires running two systems at once, because the old one has to keep shipping while the new one grows up. Two systems need roughly two sets of hands, and a team of three does not have them. Below about six engineers, the honest options are living with it and replacing one process at a time, and anything more ambitious means the robot stops improving.
A larger group can genuinely carry a rewrite, and usually should not carry all of one. The pattern that survives is a small team owning one layer's replacement, with a date, while everyone else keeps the shipping robot healthy. The pattern that fails is the whole engineering group moving to the new thing because the old thing is depressing.
There is also a knowledge question underneath the headcount. If the people who wrote the original have left, a rewrite is not a rewrite, it is an archaeology project with a deadline attached, and the estimate will be wrong in the direction described in why teams outgrow their first framework.
Does the hardware you shipped on decide whether a rewrite is possible?
Frequently it does, because deployed hardware sets the pace at which anything new can reach a real machine. If your robots are in customer buildings, every change has to survive a rollout, a rollback plan and somebody's operations schedule. That alone makes the strangler approach the only realistic route, since it lets a single replaced process ride out on a normal release instead of requiring a flag day.
Hardware also decides which destinations are open. If your compute is a small board already fully occupied, moving to a heavier framework is a decision about memory and cores rather than about taste. If the robot spans several computers, the network is a constraint that no rewrite removes. If everything runs on one box, options exist that do not exist otherwise, which is the ground covered in whether shared memory is what you need.
The trap is planning the replacement against the development bench. Benches have spare cores, clean networks and no dust. Put any candidate on a robot that has been running in the real environment for months before the plan hardens around it.
How much does your deadline change whether you should rewrite?
A deadline inside two quarters removes the rewrite from the table entirely, and a long runway makes replacing one layer cheap. Rewrites do not fail because engineers are slow. They fail because the estimate covers the code and not the knowledge, and the knowledge only reveals itself when the new version meets a real machine on a bad day.
Deadlines also warp judgement in a specific direction. Under pressure, a fresh start feels faster than understanding the existing system, because the first week of writing produces visible progress and the first week of reading produces confusion. The comparison is dishonest: you are weighing your first week against everything the old system already survived.
If the deadline is fixed and the code is genuinely obstructive, the move is to carve out the single process standing between you and the date, replace only that, and leave the rest untouched until after the delivery. Teams that do this often find the urge to rewrite disappears once the specific obstruction is gone, which is itself useful information about what the complaint really was.
What if the team has never shipped a rewrite before?
Then keep the scope to one process, because a first replacement is where a team learns what it does not know about its own robot. The skills a rewrite needs are not the skills of writing good code. They are the skills of proving that two versions behave the same, of running both at once, of switching back quickly when the field disagrees with the bench, and of resisting the temptation to add features while rewriting.
That last one deserves emphasis. Almost every failed rewrite added capabilities during the rebuild, which destroyed the only reliable test available, namely whether the new version does exactly what the old version did. The moment the new one is supposed to be better as well as equivalent, there is nothing left to compare against and no way to know when it is finished.
A first attempt should therefore pick the process with the clearest inputs and outputs, replace it with the behaviour deliberately unchanged, run both for weeks, and only then discuss improvements. Teams that skip the boring version tend to end up in the situation described in why humanoid startups keep rewriting.
What does a rewrite look like when it goes wrong?
It looks like two robots, and neither one is finished. The old system stops receiving improvements because everyone considers it dead, and the new system cannot ship because the last handful of behaviours turn out to be the ones nobody understood. Bug reports arrive against the old version and get answered with a promise about the new one. That promise slips twice, and the second slip is when people update their profiles.
There is a recognisable middle stage. The demo of the new stack is genuinely better, in a lab, on a clean machine, with the good cable. Then it meets the customer site with the poor lighting and the loose connector, and each week is spent rediscovering a workaround that already exists, in the old codebase, in a commit message from two years ago.
The tell that things have gone wrong is a calendar one. If the replacement has been almost ready for more than a month, it is not almost ready. The remaining work is the undocumented knowledge, and that portion does not shrink on a schedule.
What do you give up by rewriting?
You give up every lesson the old system absorbed without recording, and the loss arrives as a series of small field surprises rather than one large failure. The retry that made a flaky sensor usable. The order the motors must be enabled in. The reading discarded on startup because one unit reports nonsense for a moment after power-up. None of it is in the design document, and all of it will be relearned in front of somebody who is paying.
You also give up momentum in a way customers notice. During the rebuild, the robot's actual capabilities stand still, and standing still is invisible internally and very visible externally. Competitors ship. Support requests queue behind a version nobody wants to touch.
The other side of the ledger deserves the same honesty, because living with it has costs too. Every quarter spent nursing an obstructive design is a quarter of slower changes, harder hiring and accumulating workarounds. The choice is not between a cost and no cost. It is between a cost you pay in one visible block and a cost you pay in small instalments forever.
When is ROS 2 the better choice?
ROS 2 is the better destination whenever what you are missing is an ecosystem rather than a design. Teams that built their own stack early usually reach a point where the shortage is tooling: no way to record a failure in the field and replay it at a desk, no visualisation, no driver for the sensor just ordered, no way for a contractor to be useful in week one. ROS 2 supplies all of that, and the port is a well-trodden path with people who have done it before.
ROS 2 also wins whenever the hard problems are mapping, navigation, perception or fleet coordination. Those are years of work that already exist and that no small team should write again, whatever the state of their current code.
HORUS is not the answer in those cases. A shared-memory middleware carries messages between Rust, Python and C++ processes on one machine and brings no mapper, no planner, no driver and no ecosystem, so a team whose complaint is missing tooling would be choosing to build all of it. The cost of that mistake is covered in what a move off ROS 2 costs and keeps.
Is the codebase really the reason the robot is late?
No, and here is why. Late robots are usually late for reasons that sit outside the source tree, and a rewrite touches none of them. The most common are an unclear specification of what the machine is supposed to do, hardware that changed after the software was written, a test setup that cannot reproduce the failures customers report, and a team that has never agreed on what finished means.
Each of those survives a rewrite untouched. Worse, a rewrite hides them for months, because progress becomes measurable in modules completed rather than in robot behaviour improved, and modules completed is a metric that always looks healthy.
The diagnostic takes an afternoon. Look at the last ten things that slipped and write down the actual cause of each. If most say the code fought us, a replacement is on the table. If most say we did not know what it was supposed to do, or we could not reproduce it, or the hardware changed again, then a rewrite converts a knowable problem into a longer, less knowable one. That pattern is examined in why projects stall after the prototype.
Will rewriting in a safer language stop the crashes?
Partly, but not the way you think. Moving from C++ to Rust genuinely removes a whole family of memory faults, and that family is responsible for a real share of the crashes that leave nothing useful in a log. Teams that make the move usually stop seeing those particular failures, which is not nothing.
What the move does not touch is the larger category of robot failure, which is logic that is wrong rather than memory that is unsafe. A state machine that has no path out of an error state, a controller tuned for a load the robot no longer carries, a timeout shorter than the hardware's worst honest response: every one of those compiles cleanly in any language and stops the arm in exactly the same place.
So the language change is worth doing on the layer where a crash is unacceptable, and worth nothing on the layer where the bugs are about behaviour. Pick per layer rather than per repository, which is the same reasoning as mixing languages deliberately.
How should a team make this call in a week?
List the last twenty problems and sort them by cause, because the list decides this faster than any architectural argument. Group them into design problems, meaning the fix had to touch several places that must agree, and everything else. If the design group is small, you have ugly code and should write documentation instead of a replacement. If the design group dominates, name the single layer those problems live in, because that is your candidate and nothing else is.
Then run the cheapest possible test of the replacement. Take the candidate layer, build the smallest version that produces the same outputs from the same inputs, and run it beside the original on the real robot in the real environment for a week. Not a benchmark. The real machine, on the ugly input, with somebody watching whether the arm still stops before it hits the table.
Write the decision down with its reason, in the repository, including what you chose not to do. The next team to have this argument will be your own team, in a year.
- If your robot is shipping and the complaints are about taste -> live with it and document the rules, because taste has never blocked a delivery.
- If one module absorbs most of your bug fixes -> replace that single process behind its existing boundary, because the rest of the robot keeps working.
- If every fix touches four files that must agree -> replace the layer that forces the agreement, because the design is the defect.
- If what you are missing is drivers, recording and replay -> port to ROS 2, because the ecosystem is the part you cannot write yourself.
- If your own processes keep missing each other's deadlines -> move that layer to a shared-memory middleware, because the handoff is the complaint.
- If the original authors have all left -> live with it for one more quarter and document as you go, because you cannot rewrite what nobody can explain.
The HORUS Fit Framework reduces the destination question to five axes: ecosystem size, setup effort, team size fit, deployment target, and licence. Score every candidate on all five, and rule out anything weak on the axis your product cannot afford.
If the axis that keeps deciding it for you is the handoff between your own processes on one machine, put HORUS on your reading list rather than this quarter's plan: star it so it is in your list when you start building.