← All articles

Get new article notifications.

How to vibe code without shipping spaghetti

If you've built a couple of small apps by vibe coding, you've probably already noticed the problem. The first version works great. The second version works, but the code is starting to look a little weird. By the fifth or sixth round of changes, you're staring at files that have the same logic written three different ways, config values hardcoded in six places, and a 900-line file that does everything from database calls to HTML rendering — and the app still runs, but the code has become unworkable.

That's the default output when you let an AI run unsupervised. It's not that the AI can't write clean code — it absolutely can — it's that it won't unless you tell it what clean looks like. You can fix most of this before you ever ask for your first feature, by setting the ground rules up front and then holding the AI to them as you go.

Set the rules before you build anything

Before asking Claude to build a single feature on a new app, give it a project instruction file that spells out how the code should be organized. Not vague stuff like "write clean code" — specific rules it can follow. Here's the kind of thing that goes in there:

  • Folder structure. "Controllers go in /app/controllers. Database access goes in /app/models. Views go in /app/views. Shared helpers go in /app/lib." Pick a structure and name it. The AI will follow it if you tell it to.
  • Database access layer. "All database queries go through the model classes. No raw SQL in controllers, no raw SQL in views, ever." This one rule cuts out a large share of the spaghetti.
  • Secrets and config. "API keys, database passwords, and anything environment-specific lives in a .env file loaded at startup. Never hardcode a secret in source. Never commit .env to the repo."
  • Styling. "No inline styles. All CSS lives in /public/css. Use existing utility classes before adding new ones."
  • Error handling. "Every database call and every external API call is wrapped in try/catch. Errors are logged, not swallowed, and the user sees a friendly message."
  • Naming. "Functions are verbs, variables are nouns, constants are UPPER_SNAKE_CASE." Pick a convention and the AI will stick to it.

None of this is exotic. It's the same stuff a senior developer would set up on day one of a new project. The difference is you have to say it out loud because the AI defaults to "whatever gets this one feature working right now," and that default is what builds the pile.

Check its work as you go

The project instructions get you a clean start. Keeping it clean through 50 iterations is a different habit, and it's the one most people skip.

A few things worth doing during a session:

  • Ask it to explain its architecture choices. After it adds a nontrivial feature, ask "walk me through how this fits with the existing code — where did you put the new logic, and why there?" Half the time the answer is reasonable. The other half, the AI admits it tucked something in a weird place because it was easier than refactoring, and now you know to push back.
  • Catch duplication the moment it starts. If the AI writes a function that looks a lot like something it wrote last week, stop and say "this is doing the same job as X — pull the shared logic into a helper and have both call it." Duplication is cheap to fix in the moment and expensive to untangle a month later, when there are four slightly different versions and none of them quite match.
  • Write the test before the fix. When something's broken, say "write a test that fails because of this bug, then fix the bug so the test passes." You end up with a test suite that covers the stuff that actually breaks in real life, which is more useful than tests the AI invents on its own.
  • Run a periodic cleanup pass. Every ten features or so, ask the AI to review the whole codebase against the project instructions and flag anything that drifted — dead code, duplicated logic, hardcoded values that should be in config, files that have grown too big. Then have it fix what it found.

The tradeoff

All of this slows the vibe down a little. You're not just saying "build me a thing" and watching it appear — you're setting rules, asking questions, pushing back on shortcuts, and occasionally stopping to clean up. The first feature takes a bit longer, but the code stays workable: a new feature doesn't break two others, another developer can find their way around, and you can read the code six months from now and understand what it's doing. The alternative is a codebase where every new change takes longer than the last, until eventually you give up and start over, which is where a lot of vibe-coded projects end up.

Wondershare MobileTrans

Posts on this site contain affiliate links. As an Amazon Associate and member of other affiliate programs, I earn from qualifying purchases at no extra cost to you.

Leave a comment

Comments are moderated — I read every one. Your email is only used if I need to follow up; it isn't published. Links aren't allowed.

← All articles