Intro
Ask an AI assistant to "handle the checkout flow" and it will hand you a working function in about four seconds. Validate the cart, call three different APIs, format a receipt, log the analytics event, catch whatever errors show up, all in one block, three levels of nesting deep. It runs. It passes the one test anyone bothered to write. And it is, structurally, a small readability crisis waiting for whoever opens that file next.
That gap, between code that works and code a human can actually follow six months later, is where most of the real cost of AI-assisted development quietly accumulates. Here's where it actually shows up. (Illustrative composites drawn from common patterns, not specific incidents.)
The function that does six things because nobody told it not to
A generated function rarely sets out to do too much. It just keeps absorbing responsibility one prompt at a time: first it validates input, then someone asks it to also handle a retry, then to also log the outcome, then to also format the response for the frontend. Each addition looks reasonable in isolation. Read top to bottom six weeks later, it's a single function holding four unrelated jobs, and splitting it back apart means first reverse-engineering which lines belong to which job.
Names that describe the prompt, not the domain
Generated code tends to name things after the immediate task rather than the concept it represents: data2, tempResult, processedItems, handleStuff. None of these are wrong in the sense of breaking anything. They're wrong in the sense that a teammate reading the code six months from now has to run it mentally just to figure out what tempResult actually holds, instead of the name just telling them.
A slightly different helper function, invented every time
Ask a model to format a date in one file and it writes formatDate. Ask it again in another file, in the same session even, and it might write toDateString, doing almost the same thing with a slightly different edge case handled. Nothing is technically duplicated, so no linter flags it, but the codebase slowly fills with near-identical helpers that all do roughly the same job slightly differently, because each generation has no memory of what already exists two files over.
Style that resets at every file boundary
File A uses early-return guard clauses. File B nests every conditional three deep because that's what was statistically nearby in training for that particular pattern. Neither is "wrong" on its own, a reviewer skimming one file at a time won't necessarily flag it, but navigating the codebase starts to mean re-learning the local dialect every time you open a new file, because there isn't one.
None of this is a code-quality problem in the traditional sense, where someone wrote something sloppy and a linter catches it. It's a missing style contract problem: nothing external is constraining the model toward the conventions your team already agreed on, so by default it reaches for whatever's statistically common across its training data, which is rarely what's locally correct for your codebase.
So what do you actually do about it? One option is writing an exhaustive style guide and hoping every prompt includes enough of it, which doesn't scale past a few files and quietly rots the moment someone forgets to paste it. The other is treating readability as something enforced at the commit gate, the same way you'd enforce tests passing, independent of whether a human or a model wrote the line. Prompts don't have memory. Commits do. That's really the whole argument for moving readability enforcement from "hope the prompt was good" to "verify the commit is."
How is your team actually enforcing readability on AI-generated code right now, a style guide baked into the prompt, a linter, code review, or honestly, nothing yet?

Top comments (4)
This resonates hard. I've been working on a robotics data annotation standard, and the exact same pattern shows up everywhere — not just in AI-generated code, but in any system where the generator has no memory of what already exists.
The "near-duplicate helper invented every time" problem is especially brutal. We see it in data schemas: every lab invents their own column names for the same concept, slightly different edge cases handled differently, and nobody notices until someone tries to merge datasets.
Your framing of "prompts don't have memory, commits do" is the right mental model. We solved a version of this by defining a format contract first — a shared schema that every adapter must conform to, regardless of who or what produced the data. Enforced at the ingestion gate, not at the generation prompt.
For code, the equivalent is: don't hope the prompt follows your style — verify the commit satisfies your schema. Linters catch syntax, but who catches semantic duplication? That's the harder problem, and it's probably where the next wave of tooling needs to go.
Really appreciate you pulling in the schema angle, because that ingestion-gate framing is exactly the generalization I was fumbling toward but hadn't named cleanly. The semantic duplication problem is the one that keeps nagging at me too, because it's genuinely hard:
formatDateandtoDateStringaren't textually similar enough for a linter, aren't structurally identical enough for a dedup tool, but a human reading both files instantly goes "wait, why do we have two of these?" I suspect the tooling gap you're pointing at gets closed by something embedding-based sitting at the commit gate, flagging "this new helper is suspiciously close to three things that already exist, are you sure?" rather than trying to bake awareness of the whole codebase into every prompt. Basically the same move you made with schemas, just applied to functions: stop trusting the generator to remember, start verifying at the gate. Curious how strict you ended up being on the ingestion side, do you outright reject non-conforming data or auto-adapt it with a warning?The 'six things because nobody said not to' pattern maps to something specific in how prompts interact with token prediction: the model defaults to the most complete solution it can infer from context, and completeness in a prompt with no constraint reads as 'do everything adjacent.' The domain naming problem is similar. Without a glossary or a codebase to sample from, the model falls back on describing the operation rather than the concept. Both are missing context problems more than readability problems per se. The fix that's worked in my experience is treating team naming conventions and single responsibility rules as explicit context in the prompt rather than expected background knowledge. Have you found this scales as the codebase grows, or does the injected context start competing with the actual task description for attention?
That is a fair point because the model definitely defaults to its general training patterns when it lacks that specific context. But I have found that injecting style rules into every prompt does not scale well as the codebase grows. The prompts get cluttered with rules that compete with the actual task logic, which leads to inconsistent results or developers just ignoring the noise. I prefer treating style as a structural constraint enforced at the commit gate because commits have memory and consistency, so we get uniform readability regardless of how much context was in the original request.