Review a 40-file pull request and you will comment on naming, because naming is what a human can still hold in their head at that size. The architectural problem on line 900 of the fourth file goes unmentioned — not through carelessness, but because attention is finite and a large diff consumes all of it on the surface.
Effective review is therefore mostly a question of what reaches a human at all. Everything mechanical should be handled before that point, and the change should be small enough that a reviewer can still think.
Automate everything that has a right answer
Formatting, import order, lint rules, type errors, test coverage thresholds, dependency policy, migration safety checks — none of these deserve a human comment, ever. If a reviewer is telling someone to add a type annotation, your pipeline failed to do its job and you are paying senior engineering time for it.
Architecture rules can be automated further than most teams try. Module boundary enforcement, banned imports, a check that every new endpoint has an authorization test, a rule that migrations touching large tables require a specific label — all of these convert a recurring review conversation into a build failure that arrives in thirty seconds instead of a day.
What humans should actually look for
- Correctness under conditions the tests do not cover: concurrency, partial failure, empty and enormous inputs, retries.
- Security and authorization: who can call this, with whose permissions, and what happens with hostile input.
- Data and migration risk: is this reversible, does it lock, what happens during the rolling deploy.
- Whether the change belongs here at all — the design question that no static analysis will ever ask.
- Whether a future reader will understand why. If the reviewer had to ask, the answer belongs in a comment or the commit message.
# Make the mechanical layer non-negotiable and invisible.
repos:
- repo: local
hooks:
- id: format
entry: ruff format
- id: boundaries # architecture rules as a test, not a comment
entry: python tools/check_module_boundaries.py
- id: migration-safety # flags unbatched backfills and blocking DDL
entry: python tools/check_migrations.py
files: ^migrations/
# And make the size limit explicit, because it is the variable that matters most.
# tools/pr_guard.py: warn above 400 changed lines, require a justification label
# above 800. Not a rule about discipline — a rule about how attention works.Size is the variable nobody controls
The research and my own experience agree: defect detection falls off sharply past a few hundred changed lines, and the comment count per line falls with it. A 900-line pull request does not receive a more thorough review because it is more important; it receives a shallower one because nobody can sustain the attention.
The fix is not asking people to try harder. It is splitting work so that a refactor and a behaviour change are separate pull requests, a rename lands on its own, and a feature arrives as a sequence of reviewable steps behind a flag. That is a skill, and it is worth teaching explicitly rather than assuming.
A comment about formatting is a pipeline you did not configure, billed at a senior engineer's hourly rate.
One cultural note that outweighs the process: review latency shapes batch size. If reviews take a day, people batch their work into larger changes to reduce the number of waits — which makes review harder, which makes it slower. Teams that treat reviewing as the highest-priority interrupt end up with smaller changes, faster feedback and better review quality, all from the same intervention.