The anti-AI crowd is right about one thing: Ask a coding agent for a feature with no further instruction and you get a 400-line function that parses its own config, opens its own connections, swallows errors with an unwrap() or a bare except , and ships with a docstring that restates the function name. No tests, or tests written after the fact that assert the code does whatever it happens to do. I’ve spent the last few months building a backend in Rust with a pile of Python tooling around it, most of it with an agent in the loop, and I watched this happen enough times to stop pretending it’s a fluke. Readability, modularity, robustness and reuse… the defaults are bad on every axis (and the TDD and documentation story is even worse). Should this come as a surprise, however? The model is proficiently optimizing for the prompt it was handed, and “fix the tests, make no mistakes” has a very short gradient. Inlining is cheaper than extracting a module, because extracting requires knowing what already exists, and the model’s picture of your codebase is whatever made it into the context window. Duplication is cheaper than abstraction for the same reason, since every session starts with amnesia about your conventions. Nobody told it which error type the project uses, so it invents one. Nobody told it that doc comments explain what and never why-we-changed-it, so it narrates the diff it just wrote. The output is the industry median, and the industry median is spaghetti. Which is where I part ways with the “AI code is garbage” take. Every one of those failures traces back to how the tool got used. Picture a contractor who types at 2,000 words a minute, has read every public repo on GitHub, and shows up on day one with zero knowledge of your house style, because there is no written house style. You’d get the same spaghetti from a human in that seat. The difference is that a human takes a week to produce it, and you blame the onboarding. With the agent it takes four minutes, and people blame the agent. The fix is standards before the first line of code, written down in a place the tool reads at the start of every session . In my Rust work that meant, among many other things, pinning clippy::pedantic in the workspace manifest, denying unwrap_used and expect_used outside tests, one documented error enum per module with thiserror , and a rule that every public item carries a rustdoc comment whose example compiles as a doctest. On the Python side it was a pinned ruff config, strict type checking as a hard failure, and a rule that no implementation file gets created until a failing test exists for it. None of this is exotic. It’s the stuff every senior engineer says they do and most teams half-do . The agent will half-do it too, unless the standard lives somewhere it can’t miss. Prose gets you most of the way, while mechanism gets you the rest. A convention written in a markdown file the agent reads gets followed most of the time; a lint that fails the build gets followed all of the time, because the agent sees the failure and fixes it before it ever reaches you. So the work is moving as much of the standard as possible from “please” to “cannot”: a pre-commit hook that rejects a commit touching source without touching a test, a CI step that refuses a public function without a doc comment, a check that blocks a new file in a language the project retired. The model is good at satisfying a gate it can see. It is terrible at honoring a preference nobody encoded. The TDD default is the worst of the lot. Let one agent write the test and the implementation in a single pass and you get tests that mirror the implementation, with the same branches and the same blind spots. They pass on day one and tell you nothing. What worked for me was splitting the roles . One pass writes behavioral tests against the public interface only, I read them and throw out the ones that peek at internals, then a fresh context implements against the failing suite with no memory of how the tests were authored. Those tests survive refactors because they never knew the structure in the first place. And this is where the agent is strong: hand it a red test and it converges fast. So yes, spaghetti by default. The developers who get clean code out of these tools are the ones who did the boring standardization work up front, lints, hooks, test-first, doc conventions, and put it where the tool reads it. That’s the same work that made human teams produce clean code. The agent didn’t change the work. It changed how fast you find out YOU skipped it. submitted by /u/EON_Raider
Originally posted by u/EON_Raider on r/ClaudeCode
