Code Review: From Practice to AI Automation

kody ai code review

Code review is the process of reviewing a code change before it gets merged. A good review checks whether the PR solves the intended problem, follows the codebase’s standards, covers important cases with tests, and avoids introducing security, performance, or maintainability risks.

In practice, code reviews tend to get stuck for the same reasons: PRs that are too large, poor descriptions, overloaded reviewers, vague comments, debates over personal preferences, and little clarity around what should actually block a merge. Now that AI is writing more code, this has become even more of a problem.

It’s no longer news that part of the bottleneck has shifted from writing code to validating it.

GitLab’s own report confirms this: 85% of respondents agree that AI has shifted the bottleneck from writing code to reviewing and validating it, while 84% say that the biggest challenge with AI-generated code is governing what happens after it is created.

meme - debugging its code for next 2 hours

This guide shows you how to approach code review in a more practical way, without turning it into a heavy process or putting all the responsibility on one or two people on the team.

How to do code review in practice

A good code review starts before you open the first changed file. If you begin by reading the code line by line without understanding the purpose of the PR, you’ll spend your attention in the wrong places.

Use this workflow as a starting point:

  1. Understand the purpose of the PR.
  2. Check whether the description explains the problem and the solution.
  3. Look at the tests before diving into the implementation.
  4. Review business logic, security, performance, and impact beyond the diff.
  5. Leave comments with context, not just opinions.
  6. Approve when the code maintains or improves the quality of the codebase.

This process may sound simple, but it fixes a large share of poor reviews. Instead of searching for problems in the dark, the reviewer evaluates the change with a clear objective in mind.

What to look for in a code review

One of the most common mistakes is treating code review as diff reading. The diff is only the entry point. Sometimes the real risk is in how that change affects another module, a permission, or a business rule.

Google’s engineering practices documentation recommends looking at design, functionality, complexity, tests, naming, comments, style, documentation, and the broader system context.

In practice, I’d look at these areas in the following order.

PR purpose

Before reviewing the code, understand what the PR is supposed to deliver. If the description is empty or only says something like “flow adjustments,” ask for more context. You can’t properly review a change if you don’t know what it is trying to accomplish.

A good description should answer a few questions:

  • What problem does this PR solve?
  • What approach was chosen?
  • Which parts of the system are affected?
  • Is there a related ticket, spec, or discussion?
  • Is there anything the author wants reviewers to pay special attention to?

If the PR doesn’t explain these things, the reviewer has to figure them out from the diff. That’s a poor way to start a review.

Tests

Tests can tell you a lot about the intent behind a change. Before looking at the implementation, check whether the new behavior is covered.

  • Is there a test for the main scenario?
  • Is there a test for errors, permissions, or empty states?
  • Does the test validate behavior, or just implementation details?
  • Was an existing test removed without explanation?

When a PR changes an important business rule but doesn’t include tests for that behavior, you need to review it more carefully. The reviewer should understand the affected scenarios and decide whether additional test coverage should be required before the merge.

Business logic

The hardest part of code review is often determining whether the change actually makes sense for the product.

A PR can be technically correct and still implement the wrong behavior, leave out business rules, or fail to cover important scenarios.

That’s why the review should compare the implementation with the original requirement and verify that the expected rules, constraints, and behaviors are actually reflected in both the code and the tests.

Security

Security shouldn’t depend entirely on automated tools. Linters, scanners, and AI can help find issues, but some risks still require context about how the change works within the system.

A few basic areas should always be part of the review:

  • Is user input validated on the backend?
  • Are authentication and authorization enforced in the right place?
  • Do sensitive data appear in logs, fixtures, or error messages?
  • Does the change expose a new endpoint or modify an access surface?
  • Is there a risk of SQL injection, XSS, or unauthorized access?

The Stanford study Asleep at the Keyboard? found vulnerabilities in a meaningful share of programs generated with Copilot in security-related scenarios. I wouldn’t interpret that as meaning AI-generated code is inherently insecure. The takeaway is that code that looks correct still needs more careful validation when it involves authentication, sensitive data, or user input.

Performance

Performance review isn’t about trying to predict every problem that could ever happen. It’s about identifying changes that already show signs of becoming expensive or unstable in production.

A few things to consider:

  • Are there database queries or external calls inside loops?
  • Does the change increase the number of requests to other services?
  • Are large lists paginated?
  • Was a cache removed, or was an unbounded cache introduced?
  • Will the operation still be viable with production-scale data?

The problem is that many things work perfectly well at low volume and start degrading as usage grows. A review should try to identify that kind of risk before it reaches production.

Architecture and maintainability

A change needs to fit well within the existing architecture. That doesn’t mean blocking new approaches, but it does mean avoiding a situation where every PR introduces a different pattern or adds unnecessary complexity.

The review should check whether the implementation follows the project’s existing structure, keeps responsibilities properly separated, and avoids premature abstractions. It should also consider maintenance cost: a solution can work today while still making the next change harder.

A few questions can help guide the review:

  • Does the solution follow patterns already used in the project?
  • Is there a simpler way to solve the same problem?
  • Is the business logic in the right place?
  • Does the change introduce unnecessary dependencies or abstractions?
  • Will the code still be easy to understand and modify later?

Impact beyond the diff

A PR only shows the files that changed, but the impact of the change can extend far beyond them.

When a function changes behavior, you need to understand what depends on it. The same applies to API contracts, permissions, schemas, and any other change that could affect parts of the system that don’t appear in the diff.

In large codebases, this becomes even harder because the same function, type, or rule may be used by several modules and flows. The more dependencies there are, the greater the chance that a local change breaks something far away from where the code was modified.

That’s why reviewing only the lines in the diff isn’t enough. The review also needs to consider what consumes the change and which parts of the system could be affected by it.

Code review checklist

I like using a checklist during code review, not to add another step to the process, but to make sure important checks don’t depend entirely on who happens to be reviewing that day.

You can start with something simple like this:

Purpose

  • ☐ The PR explains what problem it solves.
  • ☐ The change is aligned with the ticket or spec.
  • ☐ The scope is small enough to review carefully.

Tests

  • ☐ The main behavior is covered.
  • ☐ Error cases, permissions, and empty states have been considered.
  • ☐ Tests validate behavior rather than fragile implementation details.

Business logic

  • ☐ The code delivers the expected behavior.
  • ☐ Permission rules are enforced on the backend.
  • ☐ Important edge cases have been handled.

Security

  • ☐ External inputs are validated.
  • ☐ Sensitive data does not appear in logs, messages, or fixtures.
  • ☐ Authentication and authorization have been reviewed.

Performance

  • ☐ There are no unnecessary queries or external calls inside loops.
  • ☐ Large lists have pagination, limits, or a clear strategy.
  • ☐ The impact at production data volumes has been considered.

Maintainability

  • ☐ The solution follows existing codebase patterns.
  • ☐ Naming and structure make the code easier to understand later.
  • ☐ Documentation has been updated where necessary.

One thing to keep in mind: if the same checklist item shows up in almost every PR, it probably makes more sense to automate that check than to rely on people remembering it every time.

How to prepare a pull request for review

The author has more responsibility for the quality of the review than it may seem. A poorly prepared PR forces reviewers to spend time reconstructing context, separating relevant changes from noise, and asking questions that could have been answered in the description.

Before requesting a review, look through your own PR as if you were the person reviewing it.

Keep PRs small and focused

post twitter

Large PRs take longer to review and make it harder to maintain the same level of attention from beginning to end. The more things packed into one change, the greater the chance that an important detail gets missed.

A good PR has a clear objective. Whenever possible, separate refactoring, formatting, and behavior changes into different PRs. This makes the intent easier to understand, reduces review effort, and lowers merge risk.

Write a useful description

A PR description doesn’t need to be an essay, but it should give the reviewer enough context.

You can use a template like this:

## What changed
[Explain in a few lines what this PR changes.]

## Why
[Explain the problem, ticket, or business rule behind the change.]

## How to test
[List commands, scenarios, or manual steps.]

## Areas to review carefully
[Highlight decisions or parts of the change that deserve extra attention.]

This saves a lot of time because the reviewer already knows where to look and why.

Run tests and checks before requesting review

Before requesting a review, it’s best to have the basics taken care of. Run the tests that make sense for the change, check lint and the build, and make sure no unrelated files slipped into the PR.

It’s also worth rereading the diff directly in the PR tool. Looking at the change in GitHub, GitLab, or Bitbucket often makes it easier to spot things you missed in the editor, such as duplicated code, a forgotten comment, or an accidental change.

This keeps the review focused on what actually matters.

Tell reviewers where you want feedback

If there’s a part of the change you’re still unsure about, say so in the PR. It might be a technical decision, a business rule, or behavior that hasn’t been fully defined yet.

This helps reviewers understand where they should pay closer attention and which decisions need a second opinion, instead of treating every part of the PR with the same level of scrutiny.

How to leave good code review comments

meme review comments

A good review comment should help the person who wrote the code understand the issue and decide what to do next. It should be specific about what is wrong, why it matters, and what the expected behavior should be.

Vague comments like “this is wrong” or “improve this name” aren’t very helpful because they push the work of interpreting the feedback back onto the author.

When it makes sense, explain the impact of the issue or suggest a possible direction. The goal isn’t to write long comments, but to make the reasoning behind the feedback clear.

Tone matters too. Feedback should be direct and respectful, and it should stay focused on the code and the technical decision rather than the person who wrote it.

When to block a merge

Not every issue found during review should block the merge. The most important thing is to understand the impact of the change.

If a comment is about a preference, a minor readability improvement, or something that can safely be improved later without increasing risk, it can remain a suggestion. When every small detail becomes a reason to block a PR, reviews lose their sense of priority and it becomes harder to distinguish what truly needs to be fixed before the code reaches production.

Blocking makes sense when there is a concrete risk in the change. That includes incomplete business logic, security flaws, missing tests for critical behavior, broken contracts, clear performance problems, or code that will be difficult to maintain in an important part of the system.

In those cases, it isn’t enough to say that the PR can’t be approved yet. The comment should explain the risk, why it blocks the merge, and what needs to change before the review can move forward.

How to avoid code review bottlenecks

Code review becomes a bottleneck when a PR enters the queue but there’s no clarity about who will review it or when that will happen. In the meantime, the author moves on to another task and the change sits idle. The longer it waits, the more context gets lost and the more effort it takes to pick the discussion back up later.

That’s why improving review quality isn’t enough. Teams also need to pay attention to the time between opening a PR and receiving the first response. Having clear ownership and giving authors some predictability already solves a large part of the problem.

Respond quickly, even if the full review comes later

Google’s engineering practices guide recommends responding to review requests within one business day. That doesn’t mean dropping everything every time a PR arrives. The point is to avoid leaving the author wondering whether anyone saw the request or when the review will happen.

If you can’t review it right away, a quick response still helps. You can say when you expect to review it or point the author to someone else who may be available. That way, they know what to expect and can decide whether to keep working on the task, wait for feedback, or move on to something else.

That small amount of predictability prevents PRs from getting forgotten in the queue and reduces the amount of time changes sit idle for no clear reason.

Distribute review responsibility

When a small number of people end up reviewing almost every PR, they become a bottleneck and the rest of the team gets less involved in the process. That’s why it helps to distribute review responsibility more intentionally, whether through CODEOWNERS, rotations, or ownership by area.

Not every PR needs to be reviewed by the most experienced person on the team. Depending on the change, different people can review different aspects, such as tests, architecture, or domain-specific rules.

The important thing is to make it clear who owns each type of decision and avoid making every review dependent on the same people.

Separate formatting from technical discussion

Formatting, imports, basic style, and other objective conventions shouldn’t consume review time. When these details still show up frequently in review comments, it’s usually a sign that more automation is needed.

Review time is better spent on areas that actually require context, such as business logic, architecture, security, performance, and maintainability.

Turn repeated patterns into rules

In larger teams, I see the same problem come up again and again: a lot of review criteria end up living in a few people’s heads.

The people who know the system best remember the exceptions, security concerns, and past decisions the team has made. The problem is that when those things aren’t documented somewhere, the same discussions keep coming back in different PRs.

To me, it makes more sense to turn those criteria into review rules. In Kodus, you can write Kody Rules in natural language and apply them at different scopes, including the organization, repository, directory, file, or pull request level. That way, standards that apply across engineering can stay global, while more specific rules only run where they are relevant.

This reduces the need for someone to remember and repeat the same feedback every time. The team defines the criterion once and can then apply it more consistently across reviews.

Some teams also prefer to version these rules alongside the code. In that case, the rules can live in the repository itself and be synced with Kodus. Organizations with many repositories can also centralize this configuration.

I like this approach because it keeps review rules in the same workflow as the code. A rule change goes through a PR, stays in version history, and can be reverted if necessary.

Tune automation so it doesn’t become another source of noise

I think automation only helps when the team can trust what it’s doing. If a tool leaves too many comments, reviews files nobody cares about, or flags things that should already be handled by linting, the outcome is predictable: people start ignoring it.

That’s why I’d configure automated reviews around the team’s actual workflow. In Kodus, reviews can run automatically when a PR is opened, run again after new pushes, or be triggered manually. You can also ignore files such as lockfiles, define base branches, limit the number of suggestions, and filter what appears by severity.

This kind of configuration matters because teams don’t all work the same way. Some want automatic feedback after every change. Others want to avoid triggering another review while a PR is still receiving several pushes. And some only want automated review to run once the change is ready for a more thorough pass.

To me, the best sign that automation is configured well is that it fits into the workflow without creating extra work just to manage its own comments.

Reviewing AI-generated code

I would review AI-generated code with the same care as any other change, and in some cases, with even more scrutiny. The code can look correct, be well organized, and pass the tests while still missing a business rule, applying a permission incorrectly, or introducing a pattern that doesn’t make sense for that codebase.

To me, the biggest risk is context. The model responds to what it was given in the prompt, but the PR has to work inside a real system, where rules also live in existing code, tickets, documentation, and decisions the team has accumulated over time.

When reviewing AI-generated code, I’d pay particular attention to:

  • business rules that weren’t explicit in the prompt;
  • permissions and data isolation;
  • test coverage for important scenarios;
  • consistency with existing codebase patterns;
  • dependencies or abstractions added unnecessarily;
  • more complexity than the problem actually requires.

AI can significantly speed up implementation, but that doesn’t reduce the need to validate whether the change actually makes sense within the system before it gets merged.

The role of AI in code review

To me, AI works best in code review when it helps the team get to the parts of a change that actually deserve attention faster. It can summarize the change, apply rules the team has already defined, pull context from other parts of the codebase, and surface risks that would be easy to miss during a quick read.

That doesn’t mean making AI responsible for the merge decision. That decision still needs to account for things that depend heavily on product and system context, such as architecture, expected behavior, the impact of the change, and the level of risk involved.

I like to think of the process in three layers:

Layer Role in code review Examples
Traditional automation Handles deterministic checks that can be validated with clear, objective rules. Linting, formatting, type checking, tests, and simple checks.
AI Handles areas that require more context and interpretation. Understanding the PR, connecting related files, identifying risks, and applying team-specific rules.
Human reviewer Focuses on decisions that truly require judgment. Product behavior, architecture, impact of the change, risk, and trade-offs.

Another factor I’d consider is the model being used for the review. Depending on the team, cost, privacy, and control over where code is processed can be just as important as the quality of the comments.

With Kodus, for example, teams can use BYOK and choose the provider and model that make sense for each context. A team might use more capable, expensive models for critical parts of the codebase and cheaper options for simpler reviews, or simply want more control over cost and data processing.

How to use context during code review

In my view, a PR should almost never be analyzed from the diff alone. Behind every change there’s a task, a business rule, repository conventions, and decisions the team has already made. When that context isn’t available during review, the reviewer has to reconstruct it manually to understand whether the implementation actually makes sense within the system.

Some of that context can be brought directly into the review tool. In Kodus, for example, Kody Rules can use information from the pull request, file references, and MCP functions. This allows the review to consider not just the changed code, but also other information that helps determine how the change is supposed to work.

In practice, I’d use that context in a few ways:

  • Global rules for standards that should apply across the entire codebase, such as preventing sensitive data from being written to logs.
  • Directory-level rules when one part of the system requires special care, such as changes under src/auth.
  • Repository-level rules when one project has requirements that don’t apply elsewhere, such as requiring specific tests for an API.
  • File references when an implementation should follow a pattern that already exists elsewhere in the codebase.
  • MCP when some of the context needed to review the PR lives outside the repository and needs to be retrieved from another tool.

You can also use memories to maintain persistent instructions about how the codebase works. I find this especially useful for patterns that come up repeatedly in reviews but aren’t simple enough to turn into lint rules. That might include where certain validations should happen, how modules are organized, or which architectural decisions need to remain consistent.

This means the review depends less on someone remembering the entire history of the project. Context that previously had to be remembered and explained in every PR becomes part of the review process itself.

Metrics for improving the code review process

Code review metrics help you understand where the process is losing time and where reviews are failing to work well. They can show, for example, whether PRs wait too long for a first response, whether a small number of people handle most reviews, whether changes are getting too large, or whether certain types of issues continue to slip through review.

I prefer looking at a small number of metrics, as long as they help explain what is happening in the workflow. The most useful ones tend to be straightforward:

Time to first review

This metric shows how long a PR waits before someone starts reviewing it. I like looking at it because long wait times are usually a sign that a queue has formed somewhere in the process.

When that happens frequently, the problem isn’t just idle time. The author switches context, the change is no longer fresh in their mind, and by the time feedback arrives, getting back into the discussion takes more effort.

Time to merge

While the previous metric shows how long a PR waits before review begins, time to merge helps you understand how long the entire process is taking.

If that time starts increasing, I’d try to find where the delay is coming from. PRs may be too large, too few people may be handling reviews, or important decisions may only be getting discussed after the code is already finished.

That’s why I wouldn’t look at the number in isolation. The useful part is understanding which stage of the workflow is increasing the total time.

PR size

PR size alone doesn’t tell you whether a review will be good or bad, but it becomes useful when analyzed alongside other metrics.

The larger the change, the more context a reviewer has to keep in mind at once. That makes it harder to understand all the relationships between files and identify impacts that aren’t directly visible in the diff.

I’d pay particular attention to the number of lines and files changed. If very large PRs show up frequently and also take longer to review, that may be a sign that changes should be split up more effectively before they reach review.

Reviewer distribution

It’s also worth looking at who is doing the reviews.

If the same two or three people appear on almost every PR, there’s a good chance the team has become dependent on them. That not only increases wait times, but also concentrates knowledge and reduces the rest of the team’s involvement in code-related decisions.

In that case, the metric can help identify where review responsibility should be distributed more broadly and where more people need to become comfortable reviewing certain parts of the system.

Post-merge defects

To me, this is one of the most important metrics because it connects code review with what actually happened after the change reached production.

When a bug gets through review, I’d try to determine whether there was a reasonable opportunity to catch it before the merge. If there was, it’s worth figuring out what was missing from the process: context, tests, a clearer rule, the right reviewer, or a check that could have been automated.

The point isn’t to use this metric to figure out who let the bug through. It’s to find patterns and improve the process so the same type of problem is less likely to happen again.

Frequently asked questions about code review

What is code review?

Code review is the process of reviewing a code change before it is integrated into the main branch. It usually happens in a pull request or merge request, where one or more people review the implementation, tests, and the impact of the change on the rest of the system.

How do you do a good code review?

I’d start by understanding what the PR is supposed to solve. Without that context, it’s difficult to judge whether the implementation is actually correct.

Then I’d look at the tests, business logic, security, performance, architecture, and possible impacts beyond the diff. During the review, comments should clearly explain what the problem is, why it matters, and, when appropriate, what needs to change before the merge.

What should you look at first in a PR?

To me, the best starting point is understanding the intent behind the change. That’s why I’d begin with the PR description and the tests.

The description helps explain what problem is being solved, while the tests show which behaviors the author expects to preserve or change. With that context, it becomes much easier to read the implementation and spot anything important that may have been left out.

What is the ideal PR size?

There isn’t a single number that works for every team. What matters most is that the PR has a clear objective and is small enough for someone to understand the change without having to reconstruct an entire part of the system.

When one PR mixes a feature, refactoring, formatting, and unrelated changes, the review tends to become harder. In those cases, it usually makes sense to split the work into smaller changes.

Can AI replace human code review?

I don’t see AI as a replacement for human code review. It works better as an additional review layer.

AI can analyze a PR earlier, apply team rules, identify suspicious patterns, and retrieve context from other parts of the codebase. Even so, decisions about product behavior, architecture, risk, and trade-offs still depend on people who understand how the system works and why certain decisions were made.

How should you review AI-generated code?

I’d pay special attention to context. AI-generated code can look correct and even pass the tests while still overlooking a business rule, a permission, or an architectural decision that wasn’t explicit in the prompt.

That’s why it’s worth checking whether the change follows existing codebase patterns, respects access rules, includes tests that actually cover important behavior, and solves the problem it was supposed to solve. The speed at which the code was produced shouldn’t reduce the level of validation before the merge.

Next steps

If your team’s code review process is getting slow, I’d start with the basics before adding more process. Smaller PRs, better descriptions, clearer ownership, faster responses, and automation for repetitive checks already remove a lot of friction.

After that, I’d look at the context available during review. The easier it is to understand why a change exists, which rules it needs to follow, and what it could affect beyond the diff, the better the merge decision tends to be.

This becomes even more important now that AI is increasing the speed at which code is produced. If more changes are reaching review, the challenge is no longer just writing code, but being able to validate that code without turning review into the next bottleneck.

At Kodus, that’s exactly where I see the most value: turning the standards your team already follows into review rules, applying those rules to the right parts of the codebase, and bringing more context into the PR before the merge decision.