Code Quality: Best Practices for Better Pull Requests

kodus - pull requests

Code quality is a software system’s ability to remain easy to understand, change, test, and review as the product grows. In day-to-day work, this shows up very clearly in a pull request: the change has a clear scope, the tests protect the right behavior, and the reviewer can understand the impact without having to reconstruct the entire history of that part of the system.

With AI accelerating code generation, this definition matters even more. Teams now have to deal with more changes, more suggestions, and more code that, at first glance, looks ready to merge. But writing code faster does not automatically improve the quality of what enters the repository.

When project standards are unclear, AI can repeat existing duplication, increase coupling, and reintroduce decisions the team had already tried to move away from.

That is why I would start by looking at the PR workflow. Metrics such as complexity, coverage, and duplication help surface signs of risk. Code review, CI, and automation then help the team decide what needs to change before merge.

➣ In this article, I will show you how to measure code quality, which practices work in day-to-day development, and how to build a 30-day plan to improve pull requests without slowing the process down.

What is code quality?

Code quality is the set of characteristics that makes code easy to read, modify, test, review, and operate in production. When quality is high, the intent behind a change is clear, side effects are more predictable, and the team can evolve the system without relying on knowledge held by a single person.

This definition matters because passing CI does not automatically mean the code is easy to maintain. Linting can be green, tests can pass, and the feature can work locally, while the change is still difficult to review. That happens when the PR is too large, when business logic is placed in the wrong layer, or when the impact on other parts of the system is unclear.

And the problem does not end at merge. The next change takes longer, the next bug requires more investigation, and new team members have to look for answers that should have been clear from the code itself.

For me, a useful way to evaluate the quality of a change is to ask whether someone else could safely modify it later. If that still depends on one person’s memory or on an undocumented process, the code does not explain enough to be changed safely.

How to evaluate the quality of a pull request

The pull request is where a team’s standards become most visible. This is where the team decides whether the scope is too large, whether duplication makes sense in that context, whether the tests protect the changed behavior, and whether the change respects the project’s architecture.

A PR that is easy to review usually has a few clear characteristics:

  • a small, easy-to-understand scope;
  • a description that explains the intent behind the change;
  • tests tied to the behavior being changed;
  • code that is consistent with the repository’s standards;
  • explicit risks when the change touches a sensitive area;
  • review comments focused on decisions rather than repetitive style fixes.

When those signals are missing, the review requires much more effort. Reviewers have to ask for context, open files outside the diff, manually validate scenarios that could have been covered by tests, and discuss problems that could have been prevented before the PR was even opened.

Over time, this also lowers the quality of the review itself. Someone approves the change because it looks good enough, because the PR has already gone through several rounds, or because the deadline is tight. None of those are safe criteria for deciding what goes to production.

Why AI puts more pressure on code quality

AI does not create a team’s quality problems on its own. What it does is increase the volume of changes that reach review.

When a repository has clear standards, reliable tests, and well-documented decisions, AI works on top of a more consistent foundation. But if the codebase already contains duplication, shortcuts, and implicit rules, AI can reproduce those patterns more frequently as well.

That additional pressure also shows up in our research on the state of AI code review. In the data we analyzed, PRs with declared AI co-authorship were 2.6 times larger and received about 1.6 times more review findings. The biggest difference appeared in team-specific rules: AI-assisted changes generated 2.1 times more violations of those rules.

This helps explain why good tests and generic standards are not enough.

A change can pass basic checks and still conflict with project-specific decisions that only become visible when the review understands the repository’s context.

The challenge is that AI-generated code often looks correct on a surface-level read. The structure may be clean, the names may make sense, and the tests may pass. Even so, the change may reimplement a rule that already exists in another service, directly access a layer that should go through the domain, introduce a new abstraction for a problem the project already solves differently, or ignore effects on caching, events, permissions, and integrations.

That is why review needs to go beyond how clean the diff looks. Reviewers should not spend time on repetitive comments that a tool can identify. Human attention should stay focused on decisions that require context and judgment: system impact, architecture, business logic, security, and maintainability.

AI can help a lot with that first pass, as long as it understands the repository and the team’s standards. Without that context, it tends to generate generic comments that may be correct in some cases but also add noise and make reviews more tiring.

The cost of poor code quality

For me, poor code quality rarely shows up first as an isolated technical problem. It appears as day-to-day friction. Changes start taking longer, PRs go back and forth several times, the same comments keep coming up, and the team becomes dependent on a few people to work on important parts of the system.

A few signs usually make this very clear:

  • PRs that are too large to review carefully;
  • bugs that reappear after merge;
  • tests that pass but still do not increase confidence in the change;
  • modules that only one person truly understands;
  • small changes that cause unexpected side effects;
  • reviewers approving with less attention because they are already exhausted;
  • repeated comments about the same problems.

When reviewers have to write the same thing every week, I do not see that only as a problem with the person who opened the PR. To me, it is a sign that the process still depends too much on the memory and experience of a few people.

In those cases, I would turn that knowledge into something the team can apply consistently: a PR template, a CI rule, a test, a lint configuration, short documentation, or an automated review rule.

The idea is simple: the team should not keep spending time manually fixing the same type of problem in every change.

How to measure code quality

To measure code quality, I would combine technical metrics with PR workflow metrics. Technical metrics help identify signs of risk in the code. Workflow metrics show how those risks affect review, delivery, and rework.

Among the technical metrics, I would track:

  • cyclomatic complexity;
  • maintainability index;
  • test coverage;
  • code duplication;
  • relevant code smells;
  • bugs and vulnerabilities.

In the PR workflow, I would look at:

  • average review time;
  • average PR size;
  • rework after merge;
  • frequency of repetitive comments;
  • change failure rate;
  • time to recover from a failure.

None of these metrics should decide on its own whether a change can be approved. A small PR with a minor complexity warning may be acceptable. A large PR with lower coverage and several comments about clarity, however, probably needs to be split or reworked before merge.

A metric helps show where to investigate. The decision still depends on the context of the change.

How to interpret the main code quality metrics

Cyclomatic complexity measures how many independent paths exist inside a function. The higher the number, the harder it becomes to understand the logic and test every possible scenario. When a function goes above 15, I already treat it as a sign that the change needs a more careful review.

The maintainability index tries to summarize how easy a piece of code is to understand and modify. I would mainly use this metric to track how a module or part of the system evolves over time. A drop in the index may indicate that the area is becoming harder to maintain, but the number should start an investigation, not make the decision by itself.

Test coverage shows how much of the code is executed by automated tests. Low coverage in a critical area is an obvious risk, but high coverage can also create a false sense of security when the tests do not validate the right behavior. During review, I would pay less attention to the percentage by itself and more to the scenarios the tests actually protect.

Code duplication helps identify places where the same logic appears more than once. Not every repetition needs to become an abstraction immediately. The problem becomes more serious when business logic is duplicated, because a future fix may be applied in one place and missed in the others.

Code smells help locate areas that are becoming harder to understand or modify, such as long functions, classes with too many responsibilities, inappropriate dependencies between modules, and ambiguous names. They are not bugs by themselves, but they indicate where the review should investigate more carefully.

Bugs and vulnerabilities are more direct signals. For new code, I would make the requirement explicit: the PR should not introduce critical or high-severity vulnerabilities. When an issue at that level appears in a change, it needs to be fixed before merge.

Best practices for improving code quality

I would start by keeping PRs small. The larger the change, the more context a reviewer has to hold in their head and the greater the chance that a risk will be missed. With a smaller scope, it is easier to understand the intent, validate the behavior, and spot impacts on other parts of the system.

Next, I would put the team’s standards directly into the workflow. A PR template, CI rules, a short checklist, and automated review usually work better than a long document that almost no one checks during review.

I would also clearly separate what should block a merge from what should only raise a warning. A broken build, failing tests, and a critical vulnerability should prevent approval. Increased complexity, isolated duplication, or a refactoring suggestion can appear as a comment because there may be an acceptable trade-off at that moment.

Finally, I would revisit those rules from time to time. A standard that helped six months ago may be creating noise today. When the team starts ignoring alerts frequently, that usually means the rule is poorly calibrated or no longer represents a real risk.

A good review tries to answer a few very concrete questions:

  • Is the intent behind the change clear?
  • Is the PR small enough to review carefully?
  • Was the business rule implemented in the right layer?
  • Do the tests protect the behaviors that could break?
  • Is there any duplication that may create unnecessary maintenance later?
  • Does the change follow the repository’s actual standards?
  • Does any AI-generated code look correct in isolation but conflict with system-level decisions?

These questions keep the review focused on what actually matters: whether the change can be safely maintained and evolved after merge.

For teams using AI, the last question deserves even more attention. A piece of code may be correct inside the file and still be wrong in the context of the system. The diff may look clean, the tests may pass, and the implementation may compile, while the decision still violates the architecture, duplicates an existing rule, or ignores effects elsewhere in the application.

How to use AI to support code quality

AI helps when it reduces repetitive work before the human reviewer steps in. It can identify simple duplication, long functions, drops in coverage, untested scenarios, inconsistent patterns, and recurring risks.

That means reviewers do not have to start from scratch. The review already comes with a few signals, allowing attention to stay on decisions that truly require judgment, such as system impact, architecture, business logic, security, and maintainability.

But that support only works well when the AI understands the repository’s context. Without knowledge of the project’s standards, team conventions, dependencies, and architectural decisions, it tends to produce generic warnings that increase the volume of comments without reducing risk.

That is why I would treat AI as a triage layer. It helps identify recurring problems and prioritize where the team should look more closely, but it does not replace the analysis of someone who understands how the change fits into the system.

A 30-day plan to improve code quality

Week 1: find where the team loses the most time

I would start with the main branch, but without trying to fix everything at once. The goal of the first week is to build a simple diagnosis of what is creating the most friction today.

I would run an analysis with SonarQube, Code Climate, or a similar tool to look at complexity, duplication, coverage, vulnerabilities, and the files with the highest number of findings.

Then I would compare that with recent PRs.

  • Which ones took the longest to approve?
  • Which ones went through the most review rounds?
  • Where did the same comments keep appearing?
  • Which changes created rework after merge?

For me, the first week is about separating perception from pattern. The goal is not to create a huge list of problems, but to understand where the team is spending too much energy.

Week 2: define the minimum expected in a PR

With the most recurring problems in front of me, I would turn them into a short Definition of Done for pull requests.

It could start with something like this:

  • the change has a clear scope;
  • the PR can be reviewed without requiring disproportionate effort;
  • the tests protect the changed behavior;
  • there are no new critical or high-severity vulnerabilities;
  • the intent of the code is clear;
  • unusual decisions are explained;
  • AI-generated code was reviewed using the same standards as the rest of the code.

The most important thing here is to reduce ambiguity. When the team shares a minimum definition of what it expects before merge, review becomes less dependent on each person’s individual opinion.

Week 3: remove recurring work from the review

In the third week, I would move everything that does not need to rely on human memory into CI or automated review.

I would use blocking checks for problems the team truly does not want to accept, such as a broken build, failing tests, a serious vulnerability, or a meaningful drop in coverage in a critical area.

For duplication, increased complexity, long functions, missing tests for a specific scenario, or a PR that is too large, I would keep them as signals for investigation rather than automatic decisions.

If the team uses AI for code review, I would also add the repository’s standards, conventions, and architectural rules to the tool’s instructions. Without that context, it reviews like an outsider who can only see the diff.

Week 4: remove what is only creating noise

At the end of the month, I would return to the data to understand what actually helped.

  • Did review time go down?
  • Did the comments become more useful?
  • Did rework decrease?
  • Did automated alerts catch relevant problems?
  • Is any rule being ignored because it appears too often or no longer represents a real risk?

I would review those rules based on what they are producing in practice. When a rule does not help the team identify relevant problems, it only adds noise and reduces trust in the process.

After those 30 days, I would keep a simple routine: observe the PRs, identify what continues to repeat, automate what does not require human judgment, and preserve reviewer time for the decisions that matter most.

How Kodus helps with code quality reviews

When I think about code quality, the most important part of review is not simply finding more problems. It is identifying what actually matters for that repository and that team.

That is where Kodus stands out. It is an open-source AI code review platform with no vendor lock-in. It runs directly in the pull request workflow across GitHub, GitLab, Bitbucket, and Azure DevOps, with both cloud and self-hosted options.

For me, the main value is context. Kodus combines structural code analysis with LLMs to review a change in the context of the repository as a whole, rather than looking only at the isolated diff. This makes it possible to catch issues that often slip through a more superficial analysis, such as duplicated business logic, a dependency crossing an architectural boundary, or an outdated pattern being reintroduced in new code.

Another feature I find especially useful is the ability to write rules in natural language and apply them by repository, folder, or branch. A team can define, for example, that “the domain layer must not import infrastructure” and apply that rule only where it makes sense. In monorepos or organizations with multiple squads, that level of granularity avoids forcing the same standard onto areas with different contexts.

There is also a great deal of flexibility in how the infrastructure is configured. Kodus lets teams choose the model, use OpenAI-compatible endpoints, and run in a self-hosted environment when the company needs greater control over data, cost, or operations.

In practice, Kodus expands the team’s review capacity. It combines repository context, team rules, and risk signals to identify problems that are not visible from the diff alone. That gives reviewers better information and allows them to focus their attention on architecture, business logic, security, and the impact of the change.

Code quality FAQ

What is code quality?

Code quality is a software system’s ability to remain easy to understand, change, test, review, and operate as the product grows. It includes readability, maintainability, testability, security, and consistency with the system’s architecture.

Which metrics help measure code quality?

I would look at a combination of technical and workflow metrics. The most useful include cyclomatic complexity, maintainability index, test coverage, code duplication, code smells, vulnerabilities, review time, PR size, and rework after merge.

What role does code review play in code quality?

Code review helps a team turn quality standards into day-to-day decisions. This is where the team validates the intent of the change, the risks, the tests, the architectural impact, and maintainability before merge.

Does AI improve or hurt code quality?

It depends on how AI is used in the process. It can help when it identifies risks earlier, reduces repetitive work, and considers repository context. But it can also increase rework when it accelerates code generation without reliable tests, clear standards, and sufficient review.

Code quality needs to show up in every PR

For me, code quality should not be treated as a separate review or a one-time initiative. It needs to show up in day-to-day work: smaller PRs, changes with a clear intent, tests tied to the behavior being modified, and reviews that discuss architecture, business logic, security, and maintainability rather than style alone.

With AI in the workflow, that discipline becomes even more important. The volume of changes tends to increase, and when the process is already weak, rework grows faster as well.

When review combines repository context, clear rules, and meaningful risk signals, the team can handle that volume more effectively without making the codebase harder to understand and maintain.

For me, the practical takeaway is simple: code quality means approving a change today that the team will still be able to understand, test, and modify tomorrow.