Engineering

How do you measure code review quality? A practical framework

9 min read
Abstract data visualization representing code review quality metrics

Most teams measure code review with two numbers: how long it takes to get a review, and how many comments the reviewer left. Both numbers are easy to collect from GitHub's API. Both numbers are almost entirely disconnected from whether the review caught anything useful.

A fast review with many comments can be a thorough reviewer catching real issues, or it can be a reviewer spending fifteen minutes nit-picking naming while a logic error in the business layer goes unnoticed. A slow review with two comments can be a distracted reviewer or a senior engineer who spent forty minutes tracing execution paths and left two high-value findings. The numbers do not distinguish between these cases.

If you want to improve code review quality, you need a different measurement approach.

The outcome signal: escape rate

The metric that actually matters for code review quality is how often bugs that should have been caught in review reach production. Call this the escape rate: the fraction of production bugs for which a corresponding PR was reviewed and approved without the bug being flagged.

Escape rate is not easy to compute. It requires correlating production incidents or bug reports back to the PRs that introduced the defect, and identifying whether the defect was detectable at review time. This correlation is manual work unless you have tracing infrastructure that connects incidents to commits. It is also retrospective by definition; you learn about it after the fact.

But it is the right metric because it is the one that actually reflects whether review is serving its purpose. Teams that track it, even imperfectly, make better decisions about where to improve review processes than teams that track time-to-review and comment volume.

A rough version is achievable without perfect instrumentation. When a production bug is reported and the root cause is traced to a specific commit, ask: was this change reviewed? If so, was the bug type in the category reviewers are supposed to catch? If so, it is an escape. Tracking this even informally over six months produces a more actionable picture than any review throughput metric.

Coverage: what percentage of the diff was actually reviewed

A reviewer who approves a 600-line PR after three minutes has not reviewed it. The approval is a fiction. But GitHub's approval mechanism does not distinguish between a three-minute approval and a forty-five-minute one.

A more meaningful proxy for review depth is coverage: the fraction of changed logic paths for which there is evidence of reviewer engagement (comment, suggestion, or demonstrably discussion-generating interaction). This is harder to compute but more honest.

One approximation we use internally: for PRs that pass through PRCheck, we compare the count of automated findings that were addressed before human review began against the count of human review comments. If the human review comments cluster exclusively on the same sections as the automated findings, that suggests the human reviewer may have been guided entirely by the automated findings without independently reviewing other sections. If the human comments are distributed differently from the automated findings, that suggests genuine independent review coverage.

This is not a clean metric and it should not be used to evaluate individual engineers. It is a diagnostic signal for understanding whether the review process as a whole is producing coverage.

Finding type distribution

A useful way to characterize review quality over time is to categorize what a reviewer's findings actually are. A rough taxonomy:

A reviewer who primarily leaves automated-catchable findings is spending human review attention on work that should have been handled before they opened the diff. A reviewer who primarily leaves style findings may be reviewing effectively in that area but missing correctness issues. A reviewer with a healthy mix weighted toward correctness and design is doing the job well.

Tracking finding type distribution per reviewer over time reveals patterns. Some reviewers are thorough on one class and blind to another. Some PRs systematically receive only one type of finding, suggesting the review assignment is not matched to the complexity of the change. These patterns are invisible if you only look at comment count.

The cyclomatic complexity signal

One underused correlation: review quality tends to degrade as the cyclomatic complexity of the changed code increases, faster than review time increases. A reviewer spending fifteen minutes on a function with cyclomatic complexity 3 is probably covering it adequately. The same fifteen minutes on a function with cyclomatic complexity 12 almost certainly is not.

If you can compute cyclomatic complexity of the changed functions per PR (any coverage tool or static analyzer can output this), you can identify PRs where the review time is unlikely to have been sufficient given the complexity of the change. These are the highest-risk PRs in the queue.

We do not claim this is a perfect predictor. Some high-complexity functions are well-tested and well-understood by the reviewer. Some low-complexity functions have subtle side effects that require deep knowledge of the system to catch. But as a triage signal for identifying which PRs deserve extra review attention, complexity is one of the more reliable proxies available.

What to do with these metrics

The goal of measuring review quality is not to rank reviewers or create pressure. It is to identify where the review process has systematic gaps. These metrics are most useful as aggregate signals over weeks or months, not as per-PR scores.

A useful quarterly review: look at the distribution of finding types across the team. If automated-catchable findings are a large fraction of all review comments, the team should invest in expanding automated checks to handle that category before human review happens. If escape rate (even measured loosely) is high for a specific module or PR size band, that area needs process attention.

We are not saying time-to-review is irrelevant. Long review times create velocity problems and we write about that separately. But optimizing time-to-review without tracking whether the reviews that happen are actually catching anything is optimizing the wrong variable. A fast review process that misses bugs is not better than a slower process with better coverage; it is just faster at shipping defects.

Using automated check data as a baseline

One concrete benefit of running automated PR checks before human review: the automated findings become a baseline for measuring what human reviewers added. If the human reviewer's comments are fully explained by the automated findings (same files, same issue categories), the human review may not have gone beyond what the tool already surfaced. If the human review produces significant findings in areas the tool did not flag, the reviewer is adding genuine value that the tool cannot replicate.

This is not a judgment of the reviewer. The tool has different coverage than a human. But tracking the overlap over time is a useful calibration for both: it shows the team where the automated tool is already covering the review task adequately, and where human attention is producing findings the tool consistently misses. That calibration informs both rule investment (where should we add automated rules?) and reviewer assignment (which reviewer has expertise in the areas the tool consistently misses?).

Measurement only improves the process if the measurements are connected to the right outcomes. Connecting review metrics to escape rate, complexity coverage, and finding type distribution gives a picture that time-to-review and comment count cannot.

Catch issues before code ships

PRCheck reviews every pull request the moment it opens. Start in two minutes.