The way to stop AI from writing code you can't maintain six months later is to treat every AI-generated function as a draft from a fast but unfamiliar contractor: you review it before it merges, you rename things to match your project's vocabulary, you add tests that pin down what it actually does, and you refuse abstractions you didn't ask for.
The code AI hands you usually runs on the first try, which is exactly why it slips through review — working code feels finished, but working code and maintainable code are different things, and the gap only shows up when someone (often future you) has to change it.
Start with the review itself, because that is where maintainability is won or lost. Read the AI's output line by line and ask three questions: can I explain what each line does out loud, does this match how the rest of the codebase is written, and what happens when the input is empty, huge, or malformed?
AI tends to produce plausible-looking code that handles the happy path and quietly ignores the edges. It also invents helper functions and variable names that have nothing to do with your project's existing vocabulary — a function called `processData` in a codebase where everything else says `normalizeInvoice` is a small tax you pay every time you read it.
Rename on the way in, not later. Renaming is cheap now and expensive once other files depend on the name. The same goes for dependencies: if the AI reached for a library your project doesn't already use, that is a decision, not a detail.
Ask whether the standard library or an existing dependency does the job before you let a new package into your build.
Tests are the part people skip, and they are the part that saves you. AI-generated code without tests is a black box with a confident comment on top. Before you accept a function, write tests that capture the behavior you actually want — not just the example the AI used.
Take a small, realistic case: you ask an AI to write a function that parses a date string like `"2026-03-14"` into a date object. It returns working code for that input. You then test `"03/14/2026"`, an empty string, and `"2026-13-45"`.
The AI's version probably throws an unhelpful error or silently returns something wrong on at least one of those. Now you know what the code does, and the tests lock that knowledge in. Six months from now, when a bug report arrives, the test file tells you what the function promised, not what someone hoped it did. That is the difference between a five-minute fix and an afternoon of archaeology.
Documentation matters, but less than people think, and in a specific way. Don't ask the AI to write a paragraph explaining its own code — that produces fluent text that describes the happy path and hides the assumptions. Instead, leave a short comment only where the code makes a non-obvious choice: why this regex, why this fallback, why this library.
The one thing worth documenting in full is the function's contract — what it accepts, what it returns, and what it does on bad input. If you can't state that in two lines, the function is probably doing too much, and splitting it is the real fix. AI is happy to generate one giant function that does six things; you are the one who has to live with it.
Now the honest limits, because not all AI code needs this treatment. Throwaway scripts — a one-off data cleanup, a quick conversion, something you'll run once and delete — are fine as-is. So are prototypes you're using to explore an idea, where the point is speed and the code is expected to be thrown away.
The moment code becomes long-lived, shared, security-sensitive, or something other people will build on, the full review-and-test pass is not optional. Authentication, payment handling, anything touching personal data, and anything in a shared codebase all belong in that second bucket.
There's also a cost: this process is slower than accepting the output, often meaningfully so. You are trading speed today for the ability to change the code later, and that trade is worth it for code that lives on and a waste for code that doesn't. Be deliberate about which bucket you're in.
If you want a concrete pattern for catching the failures AI ships with confidence, our guide on how to stop AI from confidently shipping broken content walks through the same review discipline applied more broadly, and the pattern for stopping AI from shipping broken code covers the code-specific version in more depth.