Saxena argues that the AI-coding discourse has lacked a working vocabulary for specific recurring failure modes in LLM output. He proposes a taxonomy — over-abstraction, hallucinated APIs, defensive cargo cult, plausible-but-wrong refactors — modeled on Fowler's smell catalog, so practitioners can name and discuss the patterns they keep encountering on PRs.
The editorial argues that classic Fowler smells were structural and visible in the diff — long methods, dead branches, comments arguing with code. LLM smells are different: they pattern-match to what good code looks like, which is precisely why they slip through code review and cause more harm than obvious human mistakes.
The 264-point score and several-hundred-comment discussion did not treat the post as a novel observation. The dominant reaction was relief that someone finally named patterns commenters had been seeing on every AI-assisted PR for months, suggesting widespread tacit recognition that preceded the vocabulary.
Shubhang Saxena's writeup *Various LLM Smells* — currently sitting at 264 points on Hacker News — does something the AI-assisted coding discourse has been badly missing: it inventories the specific, recurring ways LLM-generated code goes wrong, and gives those patterns names. Not benchmarks. Not vibes. A working taxonomy, in the spirit of Fowler's original *Refactoring* smell catalog.
The smells fall into roughly four families. Over-abstraction: the model invents a factory, a strategy interface, and three levels of inheritance to solve a problem that wanted ten lines of imperative code. Hallucinated APIs: function calls to methods that don't exist on the library version you're using, often invented by interpolating between adjacent versions the model saw during training. Defensive coding cargo cult: try/except blocks wrapping operations that can't throw, null checks on values the type system guarantees, retries on idempotent reads. Plausible-but-wrong refactors: code that compiles, passes the existing tests, and silently changes a behavior the tests didn't cover.
The HN thread (264 points, several hundred comments at time of writing) confirms the pattern recognition is widespread. The consistent reaction wasn't "this is news" — it was "finally, someone named the thing I've been seeing on every PR for six months."
The interesting thing about Saxena's framing isn't that LLMs make mistakes. Everyone knows that. It's *which* mistakes, and why they're hard to catch.
Classic code smells from the Fowler era were structural — long methods, large classes, feature envy. They were visible in the diff. LLM smells are different: they pattern-match to *what good code looks like*, which is exactly why they slip through review. A human writing over-engineered abstractions usually has tells — inconsistent naming, dead branches, comments that argue with the code. An LLM generates over-engineered abstractions that are internally consistent, well-named, and accompanied by plausible docstrings. The review heuristics developed over decades of reading human code don't transfer.
This matters more as Copilot, Cursor, Claude Code, and Cody move from autocomplete to agent. When the model writes one line, you catch the smell. When it writes a 400-line refactor across six files, you skim, see no obvious bugs, and merge. The base rate of slop entering codebases is going up while the per-PR scrutiny is going down — a textbook recipe for a long-tail of weird production incidents whose root cause is "the AI wrote a check that looked defensive but actually masked a bug class."
The defensive-coding smell deserves particular attention. LLMs were trained on a corpus that overweights tutorial and Stack Overflow code, which overweights defensive patterns because those examples have to work in arbitrary contexts. Production code in a typed, validated, well-bounded service doesn't need most of that armor — but the model doesn't know that. The result is code that's not wrong, just expensive: extra branches, extra exception paths, extra cognitive load, extra surface area for the bugs you actually have.
It's also worth noting what's *not* on Saxena's list. Hallucinated package names — the supply-chain attack vector Bar-Ilan researchers flagged last year, where attackers register the made-up package names LLMs suggest — is conspicuously absent, probably because it's a security concern more than a code-quality one. But it belongs in the same taxonomy: it's a smell with teeth.
Three concrete shifts worth making this quarter.
First, change what your review checklist asks. Stop asking "does this look right?" — that's exactly the question LLM output is optimized to pass. Start asking "does this match the contract?" Pull up the function signature, the type definition, the API spec. Verify the code does what the *interface* promises, not what it *appears* to do. The review question for AI-generated code isn't "is this code good" but "is this code doing what I asked, and only what I asked."
Second, lean harder on property-based and mutation testing for AI-touched code paths. Example-based tests are exactly the surface the model is optimizing to pass — it has seen millions of them. Property tests probe the behavior space the model didn't see. If you're shipping a lot of LLM-generated code, the ROI on Hypothesis (Python), fast-check (JS), or PropEr (Erlang) just went up. Mutation testing — Stryker, mutmut, PIT — catches the "passes the tests but the tests are weak" case, which is exactly the failure mode for plausible-but-wrong refactors.
Third, treat over-abstraction as the default to push back on, not the default to accept. If the model produces a factory + strategy + three interfaces for what should be a function, ask it to inline. The cheapest fix for over-engineering is to refuse it. The Cursor and Copilot UIs make it almost frictionless to accept whatever's suggested; the friction needs to come from your team's review norms.
Saxena's piece is the beginning of a literature, not the end of one. Expect the next 12 months to produce LLM-specific linters, review tools, and IDE plugins built around exactly this taxonomy — "detect hallucinated APIs against the installed package versions" is a feature waiting to ship. The honest version of the AI coding pitch was never "the model writes your code." It was "the model writes a first draft, and you review it the way you'd review a contractor's first draft." The tooling and the discipline to actually do that second part is what the next wave of dev-tools companies will be selling. Until then: name the smells, train your team to spot them, and stop merging code that just *looks* like it works.
- “(The) honest caveat:” (or “genuine caveat:”, both with the colon)- “(The) honest answer:” (again, with colon)- “The thing to internalize:”- “The smoking gun:”(really, sentences that start with “The <tag suggesting the next clause is the key point>:” are a strong tell, but those four are the
The LLM writing sameness is bad. Use LLMs to help your writing! But don't include a word they generate, even just a vocabulary adjustment, in your own output. Have them critique structure and flow, spot overused words and passive constructions and dumb picks for topic sentences. It's great
Don't forget about Contrastive Negation:> Contrastive negation is a rhetorical structure that denies a specific idea in the first half of a sentence and asserts an alternative in the second half.> It typically follows an "It’s not X, it’s Y" or "not just X, but Y" form
No ___, no ____. Just _____ or using "honest" to describe an approach.
Top 10 dev stories every morning at 8am UTC. AI-curated. Retro terminal HTML email.
> The LLM generated writing obviously felt significantly better than my own writing.A general pattern for LLMs is that they look really good at things you are bad at. What that means is that if you find yourself thinking of its output as significantly better than yours in a particular domain, the