The second pass is where good code happens.
The first pass is the model doing its thing. It works, the tests are green, the feature shows up in the UI. Cool. But if you stop there, your repo slowly turns into a landfill of almost-duplicates, oddly-named variables, and 200-line functions that secretly do one thing. I have been there. I have shipped there. I do not want to live there.
So here is the small toolkit I run on AI-generated code before I let it merge. It takes about 20 minutes per file and it is the difference between a codebase you trust and one you tiptoe around.
1. Consolidate the 3 helpers that are actually one
The model loves to generate formatUserName, formatDisplayName, and getUserLabel, all in the same file, all doing the same string concat with slightly different fallbacks. Read them side by side. Nine times out of ten they collapse into one function with one parameter. Do the collapse.
2. Flatten nested ifs into early returns
If you see a pyramid, you have work to do. Early returns are not a style preference, they are a readability cheat code.
// before
function priceFor(user, item) {
if (user) {
if (user.active) {
if (item) {
if (item.price > 0) {
return item.price * (user.isPro ? 0.8 : 1)
}
}
}
}
return 0
}
// after
function priceFor(user, item) {
if (!user?.active) return 0
if (!item || item.price <= 0) return 0
const discount = user.isPro ? 0.8 : 1
return item.price * discount
}
Same behaviour, half the cognitive load. The reader knows what disqualifies the happy path before they reach the happy path.
3. Move the magic strings to a constants block
Every "pending", "in_progress", "done" floating around the function gets a home at the top of the file or in a small enum. The reason is not aesthetic. It is that the next typo bug in three months will cost you an afternoon, and a constant makes that bug a compile error instead of a runtime mystery.
4. Rename data, result, info
These are placeholder names. The model uses them because it does not know what the thing is yet. You do. result is invoiceTotals. data is signupForm. info is shippingAddress. A good variable name is a sentence the next reader does not have to write in their head.
5. Delete the comments that translate the next line into English
You know the ones.
// loop through the users
for (const user of users) {
// check if the user is active
if (user.active) {
// add the user to the list
activeUsers.push(user)
}
}
None of those comments add a single bit of information. Delete them. Keep comments that explain why, never what. If a chunk of code really needs a what-comment to be readable, that chunk needs a better name or a function extracted out of it. Which leads to the last move.
6. Extract the one thing this function actually does, then rename it
Pick the biggest function on screen. Read it once and ask, out loud if nobody is around, what is the single sentence that describes what this thing does. If your sentence has the word and in it, the function is doing too much. Extract the second half. Rename the original to match the first half.
Then do it again. You will be shocked how often a 200-line function becomes 80 lines of a clearly-named top function calling three clearly-named helpers. That is not magic, that is just naming the seams the model left behind.
Why this matters more with AI
The model writes around twice the code it needs to. Not because it is bad, but because it is generating, not editing. It cannot see that the helper it just wrote already exists 40 lines up. It does not know that your team calls them customers and not users. It hedges with extra branches, extra variables, extra comments. All of that is fine on the first pass. None of it should survive the second.
A sharp 20-minute refactor pass on a 200-line function will, in my experience, leave you with about 80 lines. Same behaviour, same tests, fraction of the surface area. Future-you reads it in 30 seconds instead of five minutes. Future-you also catches the bug in it, because there are now fewer places for a bug to hide.
The pep talk
Here is the framing that keeps me honest. The model writes the first draft. You write the codebase. The draft is cheap, the codebase is the thing you and your team live in for years. Treat every AI completion like a junior pull request from a fast but unfocused colleague. Be kind, be quick, but actually review it.
Vibecoding is not an excuse to ship sloppy. It is permission to skip the boring parts so you have energy left for the parts that matter. The refactor pass is one of the parts that matters. Do it like you mean it. 🛠️