DEV Community

Cover image for Review Is the New Bottleneck - Engineering Process After Generated Code
James Sanderson
James Sanderson

Posted on

Review Is the New Bottleneck - Engineering Process After Generated Code

Software engineer writing code while colleagues collaborate at a shared desk

There is a failure mode I have now seen enough times to describe it as a pattern rather than an anecdote. It goes like this.

A team adopts AI assistance seriously. Velocity climbs — visibly, on the chart, for about two quarters. Everyone is pleased. Then the incident rate starts rising, and the relationship between the two takes an embarrassingly long time to establish, because the incidents are diffuse. A null check that was never there. An authorization check applied at the wrong layer. A query without an index that was fine at ten thousand rows.

The root cause is not the tooling. It is that generation throughput increased and comprehension throughput did not, and nobody adjusted the process for the new constraint.

Why review capacity is the binding constraint

The pre-2023 workflow had a useful property that nobody designed and everybody relied on: the person writing the code understood it, because writing it was how they came to understand it. Review was a second opinion on top of an existing first opinion.

That property is gone for generated code. Review is now frequently the first time anyone forms a mental model of what the code does. That is a categorically different task, and it takes longer per line — not less.

Meanwhile the volume arriving at review went up. Two curves moving in opposite directions.

The observable symptoms are consistent: pull requests get larger, review latency grows, approval quality degrades under queue pressure, and — the important one — reviewers start pattern-matching on plausibility rather than verifying behaviour. Generated code is unusually good at looking right. It has correct naming, sensible structure, and plausible error handling. It fails on things that require knowing your system: the invariant that lives in a different service, the reason that table has no index, the auth check that has to happen before the fetch rather than after.

What actually helps

Six changes, roughly in order of return on effort.

1. Cap PR size, and enforce it

The single highest-leverage change. Generated code makes large PRs effortless to produce and no easier to review. A hard cap — 400 changed lines is a reasonable starting point, excluding lockfiles and generated schemas — forces decomposition at the point where decomposition is cheap.

Enforce it in CI rather than in culture. Cultural norms lose to deadline pressure every time.

2. Require a provenance note

A one-line PR field: was this predominantly generated, predominantly hand-written, or mixed?

This sounds bureaucratic and is not. It changes what the reviewer does. Hand-written code from a colleague who understands the system carries a prior that generated code does not, and reviewers calibrate correctly when they know which they are looking at. Without the signal they apply the same prior to both, which is wrong in one direction or the other.

It also produces data. After six months you can correlate provenance against defect rate in your own codebase rather than arguing from other people's blog posts.

3. Write down what may not be generated

An explicit, short list. The specifics vary by system, but the shape is consistent:

  • Authentication and authorization logic
  • Cryptographic operations and key handling
  • Anything touching money, billing, or ledger state
  • Schema migrations on tables with production traffic
  • Access control policy
  • Data deletion and retention cascades
  • Concurrency primitives and locking

Not because generation is incapable here, but because these are the areas where plausible-looking wrongness is most expensive and least likely to be caught by tests. The rule is really about forcing a human to hold the mental model where the blast radius is largest.

4. Shift verification toward properties and integration

Generated unit tests have a systematic weakness: they tend to test the implementation that was just written, including its mistakes. A generated function with an off-by-one and a generated test asserting the off-by-one behaviour is a perfectly self-consistent pair, and it passes.

What survives this:

  • Property-based tests — invariants stated independently of implementation. "Serializing then deserializing returns an equal value." "The ledger balances after any sequence of operations."
  • Integration tests against real dependencies — a real database, a real queue, in a container. Mocks encode assumptions, and generated mocks encode generated assumptions.
  • Contract tests at service boundaries — where the expensive failures actually live.
  • Mutation testing on critical paths — the only reliable way to distinguish tests that verify behaviour from tests that merely execute lines.

Coverage percentage was always a weak signal. With generated tests it is close to meaningless, because coverage is now trivial to manufacture.

5. Automate what humans read badly

Reviewer attention is now the scarcest resource in the pipeline, so spend it deliberately. Push to tooling everything a machine does better:

  • Static analysis and type checking at maximum strictness
  • Dependency and supply chain scanning, including anything the assistant suggested — hallucinated package names that later get registered by someone else is a real attack pattern
  • Secret scanning in the pre-commit hook, not just in CI
  • Performance regression checks on hot paths
  • Automated detection of common generated-code smells: swallowed exceptions, unbounded retries, N+1 queries, missing pagination

Every item automated is attention returned to the things only a human can check — whether this code is correct for this system.

6. Review architecture separately from implementation

Generated code is usually locally sensible and globally questionable. It does not know your service boundaries, your existing utilities, or the abstraction you deliberately avoided two years ago for good reasons.

Separating architectural review from line-level review helps, because the two questions require different context and different reviewers. "Does this belong here at all?" is a different question from "is this loop correct?" and asking them simultaneously means one of them gets less attention than it needs.

The economics, stated honestly

Because this connects directly to how work gets priced.

Genuinely compressed by current tooling: boilerplate, CRUD, API clients, test scaffolding, migrations, first-draft interfaces, documentation, and orientation in unfamiliar code. Real, measurable, a meaningful minority of total effort on an enterprise build.

Not compressed at all: understanding an undocumented business process, integrating a legacy system whose author has left, resolving a data model dispute between departments, security architecture, load behaviour, regulatory interpretation, stakeholder alignment. These dominate.

Newly added: inference as an operating cost that scales with usage, evaluation infrastructure, drift monitoring — and the review burden described above.

Which is why a vendor promising fifty percent off "because AI" is describing a fantasy. The honest number is meaningfully smaller, and it arrives only for teams that adapted their process. Teams that did not adapt are not saving money; they are deferring it into a maintenance budget nobody has forecast.

The test I would apply

If you are evaluating a team — internal or external — ask two things.

What is your standard for what may not be generated? A written answer means they have thought about blast radius. No answer means every part of the system is being treated as equally safe to automate, which is not true of any system I have worked on.

How did your review process change? "It didn't" is a complete and worrying answer. Generation throughput went up by a lot. If nothing downstream adjusted, the queue is absorbing it, and queues absorb pressure by lowering quality rather than by complaining.

Full guide to US delivery models, rate reality, compliance costs, contract structures, and budgeting: Custom Software Development Services in USA: The 2026 Cost and Vendor Guide.

Business partners shaking hands after signing a software development agreement

Frequently Asked Questions

Why is reviewing generated code slower than reviewing hand-written code?

Because review is often the first time anyone forms a mental model of what the code does. Previously the author built that understanding while writing, and review was a second opinion on an existing one. Forming the model from scratch takes longer per line.

What should never be AI-generated?

Authentication and authorization, cryptographic operations, anything touching money or ledger state, migrations on production tables, access control policy, deletion and retention cascades, and concurrency primitives. These are where plausible-looking wrongness is most expensive and least likely to be caught by tests.

Are generated tests useful?

Partially. They tend to test the implementation that was just written, including its bugs, producing a self-consistent pair that passes. Property-based tests, integration tests against real dependencies, contract tests, and mutation testing on critical paths are far more reliable signals.

Does code coverage still mean anything?

Less than it ever did. Coverage is now trivial to manufacture, so a high percentage indicates that lines executed, not that behaviour was verified. Mutation testing on critical paths is the practical replacement.

What is the highest-return process change?

A hard, CI-enforced cap on pull request size. Generated code makes large PRs effortless to produce and no easier to review, and review quality degrades sharply with size. Cultural norms about PR size lose to deadline pressure; CI does not.

Top comments (0)