Refactoring After Vibe Coding Is Not Refactoring
Vibe-coded software is legacy the day it's generated — so the honest first move is finding out what it already does, not restructuring what nobody can name.
"The prototype works," the product lead said. "We just need a sprint to clean it up — refactor it a bit — and we can launch." The tool was a returns-intake screen for the retail operations team, built over a long weekend by talking to a model and accepting what it produced. It demoed beautifully. And nobody in the room could say what it did when a customer sent back an item that had no matching order in the system.
That last sentence is the whole problem, and the word "refactor" is hiding it. Refactoring carries a precondition most people skip past: you can only preserve a behaviour you can already describe and check. A returns tool nobody has characterised has no behaviour anyone can preserve — it has a set of surprises waiting for the first month-end close.
So "refactor" is the wrong word here, which makes it the wrong plan. What the team has on its hands is a rescue, and rescues start somewhere other than restructuring.
None of that is a knock on the tool, or on the person who built the prototype over a weekend — the opposite, really. It is genuinely good that someone can turn an idea into something that runs without first spending years earning their way into the profession, and most of what gets built this way is meant to be exactly that: something to play with, to try an idea against, to show around and see if it has legs. That access is worth defending, not scolding.
The care I'm arguing for is about register, not permission — keeping hobby, trial, and production honestly apart, because the cost of blurring them is real even at the small end, where an app that only irritates the people using it still sours their afternoon. Something built to be played with and something asked to hold real customers, real money, and the routines people quietly come to depend on are two different objects even when they share a screenshot, and "refactor" is the word that lets the first become the second without anyone deciding it should.
What Refactoring Quietly Requires
Martin Fowler's definition is precise and worth keeping precise: refactoring is a change to the internal structure of software that leaves its observable behaviour unchanged. In plain terms, you rearrange the inside so it's easier to work with, and from the outside nothing moves. The discipline lives in that second half. If the behaviour changes, you didn't refactor — you edited, you fixed a bug, or you introduced one.
That definition hides a requirement. To keep behaviour unchanged you have to know what the behaviour is and have some way to prove it didn't drift, which is why Fowler treats a suite of self-checking tests as the essential precondition for refactoring rather than an optional extra. The tests are the instrument that tells you the outside stayed still while you moved the inside.
Before the word applies at all, then, two things have to exist already: a description of what the code does, and a net that catches you when you change it by accident. On a mature system both are usually lying around — the team carries years of behaviour in their heads, and a test suite has grown alongside the code. The interesting case is the one where neither exists yet.
Vibe Coding, Read to the End of the Tweet
It helps to be exact about what vibe coding is, because the term has already been stretched to mean any use of a model. Andrej Karpathy, who coined it, meant something narrower and specific: a way of building where you "fully give in to the vibes" and "forget that the code even exists" — accept every diff without reading it, paste errors back with no comment, let the code grow past your own comprehension. He was careful about the scope, calling it fine for throwaway weekend projects and not much more.
Simon Willison drew the line that matters here. If a model wrote the code and you then read it, tested it, and could explain it to a colleague, that isn't vibe coding — it's software development that happened to use a model, and it is refactorable like anything else. The joke in the title isn't aimed at that. It lands only on the strict mode, the one that by design leaves behind no tests and no comprehension.
Which is the awkward join. The strict mode is exactly the one that skips both things refactoring needs. You end up holding code whose behaviour no one has described, under a net no one has built — and a plan that calls the cleanup a "refactor."
Legacy on Arrival
There's a sharper name for that situation, and it comes from Michael Feathers. In Working Effectively with Legacy Code, he sets aside the usual sense of "legacy" — old, inherited, written by someone long gone — and offers a working definition in its place: legacy code is code without tests. The point is meant to be a little uncomfortable. Age has nothing to do with it. Code can be well-written, freshly generated, and still legacy, because what makes it hard to change safely is the missing net, not the calendar.
Hold that definition next to a vibe-coded prototype and the reframe arrives on its own. The returns tool has no tests, and nobody characterised its behaviour, so by Feathers' definition it was legacy the moment the model finished generating it — untouched by human hands and already the hardest code in the codebase to change with confidence.
The newest code in the repository turns out to be the oldest.
The rescue move for legacy code is not to leap in and restructure. It's to pin down what the code actually does first — Feathers calls these characterisation tests, tests that record the current behaviour exactly as it stands, bugs included, so you have something to hold still while you change the structure. If that term is new, the one-sentence version worth keeping is this: a characterisation test doesn't check what the code should do, it captures what it does do, so you notice the instant that changes.
On the returns tool, that means sitting with the awkward inputs before touching a line of structure. What does it do today with a return that has no matching order — reject it, guess at one, or quietly create a credit? What about a partial return of a three-item order, or refund rounding on a line that was discounted twenty per cent? Each of those is a behaviour somebody downstream may already lean on. Characterising them turns "we think it does something reasonable" into "here is precisely what it does, in a test that fails the day it changes."
Why "Almost Right" Is the Expensive Part
The reason this bites harder with generated code than with code a person sweated over is the particular way model output tends to fail: rarely in the open. In METR's randomized trial, experienced developers working in codebases they knew well were nineteen per cent slower with early-2025 AI tools than without — while believing they had been sped up by twenty. The suggestions were, in the researchers' phrase, directionally correct but not exactly what was needed, and closing that gap is where the time went.
Speed of generation is not speed of delivery.
The same shape appears at population scale. In Stack Overflow's 2025 survey, the most common frustration by a wide margin — named by two-thirds of developers — was "AI solutions that are almost right, but not quite," with debugging generated code the complaint directly behind it. The developers most wary of the output were the most experienced ones, the people accountable for what ships.
There's a quiet irony in the trend data too. As generated code has spread, the fingerprint of real refactoring has faded: GitClear's analysis of 211 million changed lines found that in 2024, for the first time on record, copy-pasted lines outnumbered moved lines — the signature of code reuse — while the share of refactored lines dropped from roughly a quarter to under a tenth. That's a correlation, not a controlled result, but its direction is hard to wave away: the era that talks most about "just refactoring it" is refactoring less.
What Honest Naming Buys You
Naming the work correctly isn't pedantry — it's how the work gets priced. Call it a refactor and you've told everyone it's a tidy-up: one sprint, one developer, low risk, no new behaviour. Call it what it is — a legacy-code rescue on code that happens to be four days old — and the plan changes shape honestly.
The sequence becomes two line items instead of one. First a characterisation pass: establish what the tool does with the returns that don't fit the happy path, and lock those behaviours into tests you can lean on. Then, and only then, the restructuring the product lead had in mind — safe at last, because there is finally a net beneath it. That second step is the one people were always calling refactoring. The first step is the one the word was hiding.
That reordering moves the budget and the staffing, too. The characterisation pass is real, estimable work, and putting it on the plan stops the launch date from resting on a fiction. It also asks for a different person than the prototype did: less someone who ships a demo fast, more someone at ease reading an unfamiliar system and declining to trust it — the temperament of a rescue rather than a sprint.
The fair objection is that sometimes you shouldn't rescue the prototype at all — you should throw it away and build the real thing clean. Often that's right. But it doesn't escape the same first step, because to rebuild the returns tool you still have to know which of its quiet behaviours people have started to depend on, and the only way to know is to characterise what the prototype actually does before you delete it. Rewrite or rescue, the uncharacterised version is the one you can't safely act on.
So the prototype was never nearly done. It was a proposal that happened to run — a fast, genuinely useful proposal — and turning a proposal into a product means finding out what it does, agreeing what it should do, and only then rearranging how it does it.
None of this asks anyone to stop building this way, or faults the model that spun up the returns tool in a weekend. It asks for one honest word on the plan. That swap is what makes the cost, the sequence, and the right people visible — before the launch date quietly starts depending on them.
Which "quick refactor" on your plate right now is actually an uncharacterised rescue — and what would surface at month-end if you shipped it as it stands?
If you've taken a weekend prototype the rest of the way to production, I'd value hearing where the real cost landed — particularly the behaviour you only found once you went looking for it.