<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Codzee.io</title>
    <description>The latest articles on DEV Community by Codzee.io (@codzee_io).</description>
    <link>https://dev.to/codzee_io</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4072305%2F64f26605-4d54-48b4-9777-a1c76eb52c93.png</url>
      <title>DEV Community: Codzee.io</title>
      <link>https://dev.to/codzee_io</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codzee_io"/>
    <language>en</language>
    <item>
      <title>Your CODEOWNERS File Might Be Correct And Still Send Your PR to the Wrong Person</title>
      <dc:creator>Codzee.io</dc:creator>
      <pubDate>Mon, 31 Aug 2026 11:15:03 +0000</pubDate>
      <link>https://dev.to/codzee_io/your-codeowners-file-might-be-correct-and-still-send-your-pr-to-the-wrong-person-i8j</link>
      <guid>https://dev.to/codzee_io/your-codeowners-file-might-be-correct-and-still-send-your-pr-to-the-wrong-person-i8j</guid>
      <description>&lt;p&gt;You open a &lt;a href="https://www.codzee.io/" rel="noopener noreferrer"&gt;PR&lt;/a&gt;. GitHub looks at the changed paths, checks them against &lt;code&gt;CODEOWNERS&lt;/code&gt;, and auto-assigns a reviewer. This happens so often you probably don't think about it anymore. It's infrastructure — it just works.&lt;/p&gt;

&lt;p&gt;Except sometimes it doesn't, and it fails in a way that never throws an error.&lt;/p&gt;

&lt;p&gt;Here's a realistic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight codeowners"&gt;&lt;code&gt;&lt;span class="c1"&gt;# CODEOWNERS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;/payments/&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nf"&gt;@engineer-a&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Engineer A hasn't touched &lt;code&gt;payments/&lt;/code&gt; in eight months — they moved to a different team in Q1. Engineer B has made the last 18 commits in that directory and has reviewed most of the recent PRs that touched it. Engineer B isn't in the file.&lt;/p&gt;

&lt;p&gt;Your PR touches &lt;code&gt;payments/refunds.go&lt;/code&gt;. It gets routed to Engineer A.&lt;/p&gt;

&lt;p&gt;Nothing is misconfigured. The glob pattern matches. The username resolves to a real, active employee. GitHub does exactly what it's supposed to do. The routing logic is just missing context it was never designed to have.&lt;/p&gt;

&lt;p&gt;This is the gap this article is about: &lt;strong&gt;CODEOWNERS tells you who owns a file. It doesn't tell you who should review a specific change.&lt;/strong&gt; Those turn out to be different questions, and the difference gets bigger as your team and codebase grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What CODEOWNERS Gets Right
&lt;/h2&gt;

&lt;p&gt;Before picking it apart, it's worth being clear that CODEOWNERS solves a real problem well, and nothing below is an argument for ripping it out.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explicit ownership.&lt;/strong&gt; Anyone can look at one file and see who's accountable for a given path. No tribal knowledge required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Required reviews.&lt;/strong&gt; You can enforce that certain paths always get eyes from the right team before merge — critical for things like auth, billing, or infra-as-code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear accountability.&lt;/strong&gt; When something in &lt;code&gt;/payments/&lt;/code&gt; breaks, there's no ambiguity about who's supposed to be paying attention to that code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple, predictable behavior.&lt;/strong&gt; It's a text file with glob patterns. No ML model, no black box, no surprises. You can &lt;code&gt;git blame&lt;/code&gt; it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a small team, or for stable, slow-moving parts of a codebase, this is close to sufficient. The problem shows up specifically as team size and code churn increase — which is exactly when the cost of getting routing wrong also goes up.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Where Static Routing Breaks
&lt;/h2&gt;

&lt;p&gt;CODEOWNERS answers one question — &lt;em&gt;who owns this path&lt;/em&gt; — and organizations often treat that as a proxy for a different question: &lt;em&gt;who should review this change&lt;/em&gt;. Here's where those diverge:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Static CODEOWNERS&lt;/th&gt;
&lt;th&gt;Dynamic routing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File ownership&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recent contribution&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current expertise&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review history&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reviewer load&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expertise decay&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Walking through the "No" column:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recent contribution&lt;/strong&gt; — CODEOWNERS has no time dimension. An entry from two years ago carries the same weight as one from yesterday.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Current expertise&lt;/strong&gt; — "Limited" because ownership is often a reasonable proxy for expertise &lt;em&gt;at the moment the file was written&lt;/em&gt;, but it's frozen after that. It doesn't update when someone's knowledge of an area grows or fades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review history&lt;/strong&gt; — the file doesn't know who's actually been reviewing changes in an area, only who's nominally responsible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviewer load&lt;/strong&gt; — &lt;a href="https://www.codzee.io/will" rel="noopener noreferrer"&gt;https://www.codzee.io/will&lt;/a&gt; happily assign a fifth simultaneous PR to someone already buried in review requests. It has no concept of "currently busy."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expertise decay&lt;/strong&gt; — there's no mechanism for an owner's relevance to degrade over time as they stop working in an area, and no mechanism for someone else's relevance to rise as they start.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is a criticism of CODEOWNERS as a tool — it's just outside its scope. It's a static ownership file, not a reviewer-relevance model. The mistake is using it as if it were the second thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Model Expertise as Something That Changes
&lt;/h2&gt;

&lt;p&gt;The core idea: &lt;strong&gt;expertise isn't binary, and it isn't permanent.&lt;/strong&gt; "Alice owns payments" is a snapshot. What you actually want to know is closer to "how relevant is Alice's expertise, right now, to this specific diff."&lt;/p&gt;

&lt;p&gt;Useful inputs for that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Last contribution date&lt;/strong&gt; — when did this person last touch this area?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Number of recent relevant changes&lt;/strong&gt; — not lifetime commits, recent ones. Someone with 3 commits last month is a stronger current signal than someone with 200 commits two years ago.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Similarity to the current PR&lt;/strong&gt; — has this person touched the same files, the same functions, the same data model recently?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review activity&lt;/strong&gt; — have they been reviewing PRs in this area, even if they haven't authored changes there?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical ownership&lt;/strong&gt; — were they ever formally responsible for this, even if the file has since changed?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can express this as a rough conceptual model — not a formula anyone should treat as an industry standard, just a way to make the tradeoff explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;reviewer_relevance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ownership_weight&lt;/span&gt;
                    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;expertise_weight&lt;/span&gt;
                    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;recency_weight&lt;/span&gt;
                    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;load_penalty&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual weights are going to be organization-specific, and honestly team-specific within an org. A security-critical path might want ownership weighted heavily no matter what. A fast-moving product surface might want recency and review history to dominate. The point of writing it this way isn't to hand you a formula — it's to make explicit that "who reviews this" is a function of multiple decaying/growing signals, not a single static lookup.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Work Through a Sample PR
&lt;/h2&gt;

&lt;p&gt;Say you've got a repo with three top-level domains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/payments/
/identity/
/notifications/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A PR comes in touching &lt;code&gt;payments/refunds.go&lt;/code&gt;. &lt;code&gt;CODEOWNERS&lt;/code&gt; says &lt;code&gt;payments/&lt;/code&gt; belongs to Engineer A. Here's how three candidate reviewers actually stack up:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reviewer&lt;/th&gt;
&lt;th&gt;Ownership&lt;/th&gt;
&lt;th&gt;Recent work&lt;/th&gt;
&lt;th&gt;Similar reviews&lt;/th&gt;
&lt;th&gt;Current load&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Engineer A&lt;/strong&gt; is the listed owner, but hasn't done recent work in this directory, and is already carrying a heavy review load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engineer B&lt;/strong&gt; isn't in CODEOWNERS at all, but has been actively working in this area recently and has reviewed similar changes — they've probably seen the exact class of bug this PR could introduce.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engineer C&lt;/strong&gt; has moderate ownership and moderate recent activity, low similarity to this specific change, but has bandwidth right now.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A purely static system routes to A every time, because A is the only name it knows how to check against. But on the actual signals that predict a useful review — recent, hands-on context and demonstrated familiarity with similar changes — B is the stronger candidate, and C is a reasonable fallback if B is unavailable.&lt;/p&gt;

&lt;p&gt;This doesn't mean A should be removed from the review entirely — for something in a sensitive path, keeping the formal owner in the loop for accountability might still make sense. It means A shouldn't be the &lt;em&gt;only&lt;/em&gt; signal the system checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. A Practical Routing Framework
&lt;/h2&gt;

&lt;p&gt;Distilling this into something you could actually reason about (or build) for your own repo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ownership&lt;/strong&gt; — who's formally accountable, per CODEOWNERS or equivalent?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recency&lt;/strong&gt; — who's touched this code lately?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expertise&lt;/strong&gt; — who has a track record of relevant, substantive contributions here?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review history&lt;/strong&gt; — who's reviewed similar changes before, and reviewed them well (not just approved fast)?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load&lt;/strong&gt; — who has the bandwidth to actually do this review in a reasonable timeframe?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these should unilaterally win. Ownership keeps governance intact — you don't want a recency model quietly routing security-sensitive changes away from the team accountable for them. Load is a modifier, not a primary filter — someone who's slightly busy but the clear best fit is often still the right call over someone who's fully free but has no context. The five signals are meant to be combined, with the weighting depending on what kind of change you're looking at (a one-line config tweak needs way less of this than a cross-cutting refactor).&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The Routing-Decay Loop
&lt;/h2&gt;

&lt;p&gt;Here's the pattern that causes CODEOWNERS files to quietly go stale, laid out as a loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;People change teams / roles
        ↓
Code changes (new services, refactors, deprecations)
        ↓
Expertise shifts (some people gain context, others lose it)
        ↓
Ownership file stays the same (nobody's job to update it proactively)
        ↓
Routing quality declines
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing in that loop is anomalous — it's just normal organizational and codebase churn. The failure isn't that people move around or that code changes. The failure is that CODEOWNERS has no way to hear about any of it. It's a file, not a feed.&lt;/p&gt;

&lt;p&gt;The fix isn't "update the file more often" — that's a losing battle against how fast real orgs change. It's closer to: &lt;strong&gt;continuously derive dynamic signals from data you already have&lt;/strong&gt; (commit history, PR review history, recent activity) and layer them on top of the static file, rather than trying to keep the static file itself perfectly current. Contribution and review data refresh automatically as people work — no one has to remember to edit a YAML-adjacent file when Bob becomes the de facto payments expert. The signal is already sitting in your git history and PR review logs; it's just not being used for routing.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. What Teams Can Measure Today
&lt;/h2&gt;

&lt;p&gt;You don't need new tooling to start getting visibility into how well your current routing is actually working. Most of this is derivable from data already in GitHub:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reviewer reassignment rate&lt;/strong&gt; — how often does the auto-assigned reviewer get swapped out before the PR merges? High rate = your default routing isn't matching reality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time to first &lt;em&gt;meaningful&lt;/em&gt; review&lt;/strong&gt; — not first comment, first substantive review. If the assigned reviewer routinely takes days to engage, they may be overloaded or not the right fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review load concentration&lt;/strong&gt; — what percentage of review volume in a given area lands on one person? If it's consistently one name, you likely have a bottleneck, whether or not that person is technically the best expert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PRs routed to inactive contributors&lt;/strong&gt; — a rough version of this is: for each CODEOWNERS entry, check &lt;code&gt;git log --since="6 months ago" -- &amp;lt;path&amp;gt;&lt;/code&gt; and see if the listed owner shows up. If not, that entry is a candidate for drift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review handoffs&lt;/strong&gt; — PRs where the review gets passed from one person to another before merge are a decent proxy for "the first routing decision missed."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recent contribution coverage&lt;/strong&gt; — what share of merged PRs in an area were reviewed by someone who'd actually committed to that area in the last N weeks?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these require a new system to start tracking. A few are a &lt;code&gt;git log&lt;/code&gt; or a GitHub API query away. The value isn't in any single metric — it's in noticing, over a few months, whether your routing is drifting in a direction nobody's paying attention to.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Practical Takeaway
&lt;/h2&gt;

&lt;p&gt;None of this is an argument for dropping CODEOWNERS. It's still the right mechanism for encoding accountability — who's on the hook for a given part of the codebase, and which teams need guaranteed visibility into changes that touch their area.&lt;/p&gt;

&lt;p&gt;What it's not sufficient for, on its own, is figuring out who should actually review a given PR today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep CODEOWNERS for accountability. Add dynamic signals for relevance.&lt;/strong&gt; Ownership answers "who's responsible." Recency, expertise, review history, and load together answer "who's actually the right person to look at this, right now." A routing setup that only asks the first question will keep sending PRs to people who used to be the right answer. A routing setup that ignores the first question entirely gives up the governance guarantees CODEOWNERS exists to provide. You want both.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How are you handling reviewer routing today&lt;/strong&gt; — static CODEOWNERS, team rotation, tribal knowledge ("just tag whoever's around"), or something more dynamic? Curious what's actually working at different team sizes — drop your setup in the comments.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>git</category>
      <category>github</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Our AI code reviewer problem wasn't that it was wrong. It was that it wouldn't shut up</title>
      <dc:creator>Codzee.io</dc:creator>
      <pubDate>Thu, 27 Aug 2026 13:16:37 +0000</pubDate>
      <link>https://dev.to/codzee_io/our-ai-code-reviewer-problem-wasnt-that-it-was-wrong-it-was-that-it-wouldnt-shut-up-5b5</link>
      <guid>https://dev.to/codzee_io/our-ai-code-reviewer-problem-wasnt-that-it-was-wrong-it-was-that-it-wouldnt-shut-up-5b5</guid>
      <description>&lt;p&gt;I've been thinking about a problem with AI code review that I don't see discussed enough: a reviewer can be technically correct and still make the whole review experience worse.&lt;/p&gt;

&lt;p&gt;Here's the PR that made this click for me. Small change, maybe 200 lines, nothing exotic. New validation path, a couple helper functions. The kind of thing that used to get a comment or two and an approve.&lt;/p&gt;

&lt;p&gt;The AI reviewer came back with something like a dozen comments. A naming suggestion. A "this null check might be redundant" note. A theoretical race condition that would only show up under conditions the code was never going to hit in prod. A "you could extract this into a function" suggestion on a function that was already about ten lines long.&lt;/p&gt;

&lt;p&gt;And here's the annoying part  most of these weren't &lt;em&gt;&lt;a href="https://www.codzee.io/" rel="noopener noreferrer"&gt;wrong&lt;/a&gt;&lt;/em&gt;. The variable name genuinely could've been clearer. The null check probably was redundant. Individually, I could defend almost every comment on that list.&lt;/p&gt;

&lt;p&gt;But there was one comment in there about an actual edge case in the validation logic that genuinely mattered. And it was sitting in the middle of eleven other comments that didn't. The dev opening the PR skimmed most of it, dismissed a few without really reading them, and nearly skipped the important one too — because by comment seven or eight, he'd already stopped expecting any of it to be worth his time.&lt;/p&gt;

&lt;p&gt;Nobody wanted "more thorough." They wanted to know what actually mattered.&lt;/p&gt;

&lt;p&gt;That's the distinction I think gets missed constantly: &lt;strong&gt;"is this technically an issue" and "is this worth interrupting someone for" are completely different questions&lt;/strong&gt;, and most tooling in this space only ever answers the first one.&lt;/p&gt;

&lt;p&gt;The assumption baked into a lot of AI review tools is basically: if finding more issues is good, finding even more must be better. But review was never just an issue-detection problem, it's a signal-to-noise problem, and honestly kind of a trust problem too. Once a reviewer starts treating a PR like a checklist to maximize, it stops mattering how accurate any individual comment is — the aggregate experience is what people remember, and the aggregate experience was "annoying."&lt;/p&gt;

&lt;p&gt;That frustration eventually became one of the reasons I started working on Codzee. Not going to turn this into a pitch — the short version is the goal isn't "find everything," it's figuring out what actually deserves a developer's attention. Still early, still figuring a lot of it out.&lt;/p&gt;

&lt;p&gt;Genuinely curious how other people think about this though:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many AI review comments do you actually want to see on a normal PR? Is there a number where it tips from "helpful" to "annoying" for you?&lt;/li&gt;
&lt;li&gt;Do you find yourself tuning out AI-generated comments after a tool has burned you with noise a few times?&lt;/li&gt;
&lt;li&gt;Where's your personal line between a useful warning and something that's just noise dressed up as a finding?&lt;/li&gt;
&lt;li&gt;Would you rather a reviewer miss a low-confidence issue entirely, or flag it anyway and let you decide?&lt;/li&gt;
&lt;li&gt;What actually makes you trust an automated reviewer, versus just tolerate it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not trying to manufacture a debate here, I'm actually unsure about some of these myself — like the miss-vs-flag question, I go back and forth on what the right default even is. Curious what this sub thinks.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>codequality</category>
      <category>coding</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>AI code review: Are more comments actually</title>
      <dc:creator>Codzee.io</dc:creator>
      <pubDate>Tue, 25 Aug 2026 12:23:34 +0000</pubDate>
      <link>https://dev.to/codzee_io/ai-code-review-are-more-comments-actually-3e2d</link>
      <guid>https://dev.to/codzee_io/ai-code-review-are-more-comments-actually-3e2d</guid>
      <description>&lt;p&gt;we’ve been thinking about something that seems easy to overlook with AI-assisted code review.&lt;/p&gt;

&lt;p&gt;Imagine a PR gets &lt;strong&gt;24 AI-generated comments&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That sounds thorough—but how many of those comments would you actually want a developer to spend time on?&lt;/p&gt;

&lt;p&gt;There’s a big difference between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“This could cause a production bug.”&lt;/li&gt;
&lt;li&gt;“This might create a security issue.”&lt;/li&gt;
&lt;li&gt;“This code could be more readable.”&lt;/li&gt;
&lt;li&gt;“You could rename this variable.”&lt;/li&gt;
&lt;li&gt;“Consider refactoring this small section.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these may technically be valid observations, but they don't have the same value.&lt;/p&gt;

&lt;p&gt;For developers, the real challenge isn't just &lt;strong&gt;finding issues&lt;/strong&gt; anymore.&lt;/p&gt;

&lt;p&gt;It's &lt;strong&gt;prioritizing them&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Too many low-value comments can create review fatigue, hide important findings, and slow down the person who actually needs to make the decision.&lt;/p&gt;

&lt;p&gt;I'm curious how other developers handle this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When using AI for code review, what do you prefer?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AI should report everything and let developers filter it.&lt;/li&gt;
&lt;li&gt;AI should only report high-confidence, high-impact issues.&lt;/li&gt;
&lt;li&gt;AI should report everything but rank comments by severity.&lt;/li&gt;
&lt;li&gt;Something else?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We've been exploring this problem at Codzee and believe that AI code review should optimize for &lt;strong&gt;signal, not comment count&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Would love to hear how this works in your team and what you've found useful—or annoying—about AI code review.&lt;br&gt;
&lt;a href="http://www.codzee.io" rel="noopener noreferrer"&gt;www.codzee.io&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How I'd Benchmark an AI Code Reviewer Before Putting It on 100 Engineers' PRs</title>
      <dc:creator>Codzee.io</dc:creator>
      <pubDate>Mon, 24 Aug 2026 12:55:11 +0000</pubDate>
      <link>https://dev.to/codzee_io/how-id-benchmark-an-ai-code-reviewer-before-putting-it-on-100-engineers-prs-1i19</link>
      <guid>https://dev.to/codzee_io/how-id-benchmark-an-ai-code-reviewer-before-putting-it-on-100-engineers-prs-1i19</guid>
      <description>&lt;p&gt;&lt;a href="https://www.codzee.io/" rel="noopener noreferrer"&gt;AI code-review tools&lt;/a&gt; are easy to demo.&lt;/p&gt;

&lt;p&gt;Give one a pull request, wait a few seconds, and it produces a handful of comments that look surprisingly useful. Maybe it catches a missing authorization check. Maybe it spots a race condition. Maybe it complains about a test that doesn't cover an edge case.&lt;/p&gt;

&lt;p&gt;That's a good demo.&lt;/p&gt;

&lt;p&gt;It is not a benchmark.&lt;/p&gt;

&lt;p&gt;If you're considering putting an AI reviewer on the pull requests of 100 engineers, the question isn't &lt;em&gt;"How good does this look?"&lt;/em&gt; It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How often does this tool find problems that matter in our code, how often does it waste developers' time, and does it improve the review process enough to justify the cost?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'd answer that question with an evaluation built around your own repositories, languages, review practices, and historical bugs.&lt;/p&gt;

&lt;p&gt;Here's how.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Build a representative PR dataset
&lt;/h2&gt;

&lt;p&gt;Start with a dataset of &lt;a href="https://www.codzee.io/" rel="noopener noreferrer"&gt;pull requests&lt;/a&gt; that looks like your actual engineering workload.&lt;/p&gt;

&lt;p&gt;Don't build it entirely from toy examples. A benchmark containing 50 artificially constructed security bugs will tell you something about the model, but not necessarily much about what happens when it encounters your production code.&lt;/p&gt;

&lt;p&gt;I'd aim for a few categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Historical production bug fixes&lt;/li&gt;
&lt;li&gt;PRs that introduced bugs but were later fixed&lt;/li&gt;
&lt;li&gt;Normal feature PRs with no known bugs&lt;/li&gt;
&lt;li&gt;Refactoring PRs&lt;/li&gt;
&lt;li&gt;Dependency updates&lt;/li&gt;
&lt;li&gt;Performance-related changes&lt;/li&gt;
&lt;li&gt;Security-sensitive changes&lt;/li&gt;
&lt;li&gt;Database/schema changes&lt;/li&gt;
&lt;li&gt;API changes&lt;/li&gt;
&lt;li&gt;Test-only changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part is preserving the context available during a real review: the PR diff, repository state, relevant files, tests, configuration, and whatever other context your normal review process exposes.&lt;/p&gt;

&lt;p&gt;You should also keep a hidden ground-truth record for each PR.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PR-1842
Repository: payments-api
Language: Go
Known issue: authorization bypass
Severity: Critical
Introduced in: PR
Fixed by: PR-1911
Expected finding: Yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For clean PRs, record that too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PR-2017
Repository: customer-service
Language: Kotlin
Known issue: None
Expected finding: No
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you both positive and negative examples.&lt;/p&gt;

&lt;p&gt;The negative examples are essential. An AI reviewer that finds something suspicious in every PR can look impressive until developers have to deal with the resulting noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Include known real-world bugs
&lt;/h2&gt;

&lt;p&gt;Historical bugs are some of the most valuable test cases you have.&lt;/p&gt;

&lt;p&gt;Look through incident reports, postmortems, security fixes, bug tickets, and reverted changes. Find bugs that were actually introduced into your codebase.&lt;/p&gt;

&lt;p&gt;Then construct benchmark cases that reproduce the review situation.&lt;/p&gt;

&lt;p&gt;For example, suppose six months ago a PR introduced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_admin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;export_all_accounts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;when the intended condition was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_admin&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;can_export_accounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;export_all_accounts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benchmark should test whether the reviewer catches the authorization problem from the PR context—not whether it can identify a deliberately planted &lt;code&gt;TODO: SECURITY BUG&lt;/code&gt; comment.&lt;/p&gt;

&lt;p&gt;This distinction matters.&lt;/p&gt;

&lt;p&gt;A useful benchmark measures performance on problems engineers genuinely encounter.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Include different bug severities
&lt;/h2&gt;

&lt;p&gt;Not every finding has the same value.&lt;/p&gt;

&lt;p&gt;A reviewer that catches one critical security issue but misses 20 minor problems may still be extremely valuable. Conversely, a tool that produces hundreds of "potential issue" comments while missing serious defects can become a productivity problem.&lt;/p&gt;

&lt;p&gt;Classify your known issues.&lt;/p&gt;

&lt;p&gt;One simple taxonomy is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;td&gt;Data loss, privilege escalation, severe security vulnerability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Production outage, major correctness issue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Significant edge case or reliability problem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Minor correctness issue, maintainability problem&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can adapt these categories to your existing incident or vulnerability classification.&lt;/p&gt;

&lt;p&gt;Then measure recall separately by severity.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Critical: 4/5 found
High:     11/18 found
Medium:   17/31 found
Low:      9/22 found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That tells you much more than a single "82% accuracy" number.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Test multiple repositories and languages
&lt;/h2&gt;

&lt;p&gt;If your organization has six repositories and four languages, don't benchmark the tool on one favorite service written in the language it handles best.&lt;/p&gt;

&lt;p&gt;Split the dataset across representative environments.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;payments-api       Go
web-frontend       TypeScript
mobile-backend     Kotlin
data-platform      Python
infrastructure     Terraform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't necessarily need equal numbers of PRs in every repository. You do need enough samples to identify obvious differences.&lt;/p&gt;

&lt;p&gt;The question isn't only "Does the tool work?"&lt;/p&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Does it work consistently enough across the environments where we're planning to deploy it?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A reviewer that performs brilliantly on TypeScript but poorly on Terraform may still be useful. You just need to know that before making it mandatory across the organization.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Give every reviewer the same context
&lt;/h2&gt;

&lt;p&gt;Benchmark fairness is surprisingly easy to get wrong.&lt;/p&gt;

&lt;p&gt;If you're comparing two AI reviewers, make the input as equivalent as possible.&lt;/p&gt;

&lt;p&gt;Give each one the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PR diff&lt;/li&gt;
&lt;li&gt;Base revision&lt;/li&gt;
&lt;li&gt;Repository contents&lt;/li&gt;
&lt;li&gt;Relevant configuration&lt;/li&gt;
&lt;li&gt;Tests&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;Existing review context&lt;/li&gt;
&lt;li&gt;Tool permissions, where applicable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't manually explain a tricky bug to one system because "it needed a little help."&lt;/p&gt;

&lt;p&gt;Likewise, don't give one tool access to information that the other cannot see unless that difference is part of the product you're evaluating.&lt;/p&gt;

&lt;p&gt;Context also needs to be documented.&lt;/p&gt;

&lt;p&gt;For each benchmark run, record exactly what the reviewer was allowed to inspect.&lt;/p&gt;

&lt;p&gt;Otherwise, six months later, you'll have two benchmark results that look comparable but weren't generated under comparable conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Measure bugs correctly identified
&lt;/h2&gt;

&lt;p&gt;The first metric I'd track is &lt;strong&gt;bug recall&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At its simplest:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug recall = known bugs correctly identified / known bugs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose your dataset contains 100 known bugs and the tool identifies 72 of them.&lt;/p&gt;

&lt;p&gt;That's 72% recall.&lt;/p&gt;

&lt;p&gt;But there's a catch: what counts as "identified"?&lt;/p&gt;

&lt;p&gt;A comment saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This code might have an issue."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;isn't necessarily equivalent to correctly identifying the actual defect.&lt;/p&gt;

&lt;p&gt;Define acceptance criteria before running the benchmark.&lt;/p&gt;

&lt;p&gt;For example, a finding might count as correct only if it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Points to the relevant code.&lt;/li&gt;
&lt;li&gt;Describes the actual failure mode.&lt;/li&gt;
&lt;li&gt;Explains why the change causes the problem.&lt;/li&gt;
&lt;li&gt;Provides enough information for an engineer to validate it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This prevents optimistic scoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Measure false positives
&lt;/h2&gt;

&lt;p&gt;Now run the other half of the experiment.&lt;/p&gt;

&lt;p&gt;Give the reviewer PRs with no known bugs and see what it reports.&lt;/p&gt;

&lt;p&gt;A useful metric is &lt;strong&gt;false-positive rate&lt;/strong&gt;, but I also like tracking the simpler number:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;False findings per PR&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If a tool produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Known bugs found:      72
False findings:       180
PRs reviewed:          100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then its 72% recall doesn't look nearly as exciting.&lt;/p&gt;

&lt;p&gt;You can also calculate precision:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Precision = correct findings / all findings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the reviewer produces 100 findings and only 40 are genuinely actionable, precision is 40%.&lt;/p&gt;

&lt;p&gt;For developers, this metric can matter enormously.&lt;/p&gt;

&lt;p&gt;A reviewer that catches everything but complains about everything isn't necessarily useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Measure duplicate and noisy findings
&lt;/h2&gt;

&lt;p&gt;False positives aren't the only source of noise.&lt;/p&gt;

&lt;p&gt;AI reviewers can report the same underlying problem multiple times.&lt;/p&gt;

&lt;p&gt;For example, one missing validation might produce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Input isn't validated."&lt;/li&gt;
&lt;li&gt;"Potential invalid state."&lt;/li&gt;
&lt;li&gt;"Unexpected value may reach database."&lt;/li&gt;
&lt;li&gt;"Consider adding defensive checks."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Four comments, one issue.&lt;/p&gt;

&lt;p&gt;I'd explicitly measure &lt;strong&gt;unique actionable findings&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can group findings that describe the same underlying defect and count them once.&lt;/p&gt;

&lt;p&gt;Also track findings that are technically true but don't deserve review attention.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This function could be refactored to reduce complexity."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That may be reasonable advice, but if your benchmark is evaluating bug detection, it shouldn't count as a valuable bug finding.&lt;/p&gt;

&lt;p&gt;A useful classification is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Correct + actionable
Correct + low value
Duplicate
False positive
Not a bug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the output much easier to analyze.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Measure severity accuracy
&lt;/h2&gt;

&lt;p&gt;Finding the bug isn't enough.&lt;/p&gt;

&lt;p&gt;The reviewer should also understand its impact.&lt;/p&gt;

&lt;p&gt;Suppose your ground truth says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authorization bypass → Critical
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the AI says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Potential code quality issue → Low
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Technically, it noticed something. Operationally, it failed an important part of the review.&lt;/p&gt;

&lt;p&gt;Compare predicted severity with your ground truth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  Actual
Predicted       Critical  High  Medium  Low
Critical            7      2      0      0
High                3     14      4      1
Medium              0      5     19      7
Low                 0      1      8     11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't need sophisticated statistics to start. Even a confusion matrix like this will show whether the tool systematically underestimates serious defects.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Measure whether developers actually accept findings
&lt;/h2&gt;

&lt;p&gt;Eventually, the benchmark has to leave the spreadsheet.&lt;/p&gt;

&lt;p&gt;Ask engineers to review the AI findings without knowing which tool produced them.&lt;/p&gt;

&lt;p&gt;For every finding, capture something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Valid issue?       Yes / No
Actionable?        Yes / No
Would fix it?      Yes / No
Would mention it?  Yes / No
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're running a live pilot, you can also measure what happens to findings in actual PRs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accepted&lt;/li&gt;
&lt;li&gt;Fixed&lt;/li&gt;
&lt;li&gt;Dismissed&lt;/li&gt;
&lt;li&gt;Ignored&lt;/li&gt;
&lt;li&gt;Marked duplicate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is arguably one of the strongest signals you can get.&lt;/p&gt;

&lt;p&gt;A finding with 95% technical accuracy but a 10% acceptance rate may not be particularly useful.&lt;/p&gt;

&lt;p&gt;A finding that engineers consistently validate and act on is much more valuable.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Measure review latency
&lt;/h2&gt;

&lt;p&gt;Don't forget the economics of the workflow.&lt;/p&gt;

&lt;p&gt;Measure how long it takes from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PR opened
    ↓
AI review starts
    ↓
AI findings available
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compare that with the existing review process.&lt;/p&gt;

&lt;p&gt;Latency matters because an AI reviewer that takes 45 minutes to analyze every PR may be technically impressive but operationally awkward.&lt;/p&gt;

&lt;p&gt;You should also test different PR sizes.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Small:   &amp;lt;200 changed lines
Medium:  200–1,000
Large:   &amp;gt;1,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A tool might be excellent on small PRs and degrade badly on large ones.&lt;/p&gt;

&lt;p&gt;That's something you want to discover in a benchmark, not after deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Measure developer time saved
&lt;/h2&gt;

&lt;p&gt;This is where the benchmark becomes a business case.&lt;/p&gt;

&lt;p&gt;Estimate the human effort associated with the findings.&lt;/p&gt;

&lt;p&gt;For example, during a pilot you might measure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PRs reviewed:                  200
Valid AI findings:              86
Findings accepted by authors:   61
Estimated review effort saved:  24 hours
Additional triage time:          7 hours
Net time saved:                 17 hours
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Be conservative.&lt;/p&gt;

&lt;p&gt;If an AI comment catches something a human reviewer would definitely have found anyway, don't automatically count the entire human review as "saved."&lt;/p&gt;

&lt;p&gt;Instead, ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How much work did this finding actually remove from the engineering process?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can estimate time through short developer surveys, sampled review sessions, or controlled experiments.&lt;/p&gt;

&lt;p&gt;The goal isn't to manufacture a perfect number. It's to get a defensible estimate.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. Repeat the evaluation over time
&lt;/h2&gt;

&lt;p&gt;One benchmark run isn't enough.&lt;/p&gt;

&lt;p&gt;AI systems change. Your repositories change. Prompting changes. Models change. Vendors ship new detection logic.&lt;/p&gt;

&lt;p&gt;Run the benchmark periodically.&lt;/p&gt;

&lt;p&gt;I'd keep a fixed &lt;strong&gt;golden dataset&lt;/strong&gt; containing a representative sample of your most important historical cases, then add new cases over time.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Baseline
├── 100 historical bugs
├── 100 clean PRs
└── 50 security-sensitive PRs

Quarter 2
├── +25 new production bugs
└── +25 new clean PRs

Quarter 3
├── +20 new production bugs
└── +20 new clean PRs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can track whether performance is actually improving.&lt;/p&gt;

&lt;p&gt;You can also detect regressions.&lt;/p&gt;

&lt;p&gt;If version 4.2 of your AI reviewer goes from 78% to 84% recall but doubles false findings, that's not simply "better."&lt;/p&gt;

&lt;p&gt;It's a trade-off.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple scoring framework
&lt;/h2&gt;

&lt;p&gt;You don't need a complicated machine-learning benchmark.&lt;/p&gt;

&lt;p&gt;Here's a framework I'd start with:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bug recall&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Precision / false positives&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Severity accuracy&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Developer acceptance&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate/noise rate&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review latency&lt;/td&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Developer time saved&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Score each category from 0–100, then calculate the weighted total.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Recall                  82 × 0.30 = 24.6
Precision               76 × 0.20 = 15.2
Severity accuracy       80 × 0.10 =  8.0
Developer acceptance    71 × 0.15 = 10.7
Noise                   85 × 0.10 =  8.5
Latency                 90 × 0.05 =  4.5
Time saved              68 × 0.10 =  6.8

Total                               78.3 / 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact weights are less important than agreeing on them &lt;strong&gt;before&lt;/strong&gt; you see the results.&lt;/p&gt;

&lt;p&gt;And I'd add one more rule: define minimum thresholds for critical metrics.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Overall score       ≥ 75
Critical bug recall ≥ 90%
Precision            ≥ 60%
P95 review latency   ≤ 10 minutes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That prevents a tool from compensating for catastrophic performance in one area with excellent performance in another.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about vendor benchmarks?
&lt;/h2&gt;

&lt;p&gt;Vendor benchmarks are useful.&lt;/p&gt;

&lt;p&gt;They can tell you how a tool performs against standardized datasets, and they're worth looking at when you're building your shortlist.&lt;/p&gt;

&lt;p&gt;But they answer a different question.&lt;/p&gt;

&lt;p&gt;A vendor benchmark asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How well does our system perform on this benchmark?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your benchmark asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"How well does this system work for our engineers, in our codebases, under our review process?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the number I'd use when deciding whether to put the tool on 100 engineers' PRs.&lt;/p&gt;

&lt;p&gt;For example, if you're evaluating a tool such as Codzee, treat its published benchmark results as one input into your research—not as the final deployment decision.&lt;/p&gt;

&lt;p&gt;The same methodology should apply to every vendor.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benchmark I'd actually run
&lt;/h2&gt;

&lt;p&gt;If I were setting this up for an engineering organization tomorrow, I'd make the process roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Select 300–500 representative PRs
              ↓
2. Label known bugs and expected severity
              ↓
3. Include clean PRs as negative examples
              ↓
4. Run each AI reviewer with equivalent context
              ↓
5. Normalize and deduplicate findings
              ↓
6. Blind-score correctness and severity
              ↓
7. Run a developer acceptance study
              ↓
8. Measure latency and engineering effort
              ↓
9. Calculate weighted score
              ↓
10. Repeat periodically
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most importantly, keep the benchmark independent from the vendor.&lt;/p&gt;

&lt;p&gt;You choose the PRs.&lt;/p&gt;

&lt;p&gt;You define what constitutes a correct finding.&lt;/p&gt;

&lt;p&gt;You decide how severity is classified.&lt;/p&gt;

&lt;p&gt;You determine which findings are useful.&lt;/p&gt;

&lt;p&gt;And you keep the dataset private enough that it can't simply become another benchmark for vendors to optimize against.&lt;/p&gt;

&lt;p&gt;That's how you turn an AI code-review demo into an engineering evaluation.&lt;/p&gt;

&lt;p&gt;The goal isn't to find the AI reviewer with the highest benchmark score.&lt;/p&gt;

&lt;p&gt;It's to find the one that makes &lt;strong&gt;your&lt;/strong&gt; code-review process better without creating a new category of developer noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would you add to this benchmark?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>I Put an AI Reviewer on Every PR. Here's What Happened After the 100th Review.</title>
      <dc:creator>Codzee.io</dc:creator>
      <pubDate>Thu, 20 Aug 2026 12:03:16 +0000</pubDate>
      <link>https://dev.to/codzee_io/i-put-an-ai-reviewer-on-every-pr-heres-what-happened-after-the-100th-review-393p</link>
      <guid>https://dev.to/codzee_io/i-put-an-ai-reviewer-on-every-pr-heres-what-happened-after-the-100th-review-393p</guid>
      <description>&lt;p&gt;At first, adding an &lt;a href="http://www.codzee.io/" rel="noopener noreferrer"&gt;AI code reviewer&lt;/a&gt; to every pull request feels like cheating.&lt;/p&gt;

&lt;p&gt;Every PR gets another pair of eyes.&lt;/p&gt;

&lt;p&gt;Every diff gets analyzed.&lt;/p&gt;

&lt;p&gt;Potential bugs are pointed out before a human reviewer even opens the PR.&lt;/p&gt;

&lt;p&gt;The first few reviews are impressive.&lt;/p&gt;

&lt;p&gt;Then something strange happens.&lt;/p&gt;

&lt;p&gt;You stop reading every comment.&lt;/p&gt;

&lt;p&gt;Not because the reviewer is always wrong.&lt;/p&gt;

&lt;p&gt;Because you learn that most comments aren't worth stopping for.&lt;/p&gt;

&lt;p&gt;That's the part of AI code review I think we don't talk about enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reviewer's first job isn't finding bugs
&lt;/h2&gt;

&lt;p&gt;It's earning the developer's attention.&lt;/p&gt;

&lt;p&gt;Imagine this PR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI review

✓ Possible null handling issue
✓ Consider extracting this function
✓ This variable could be renamed
✓ Potential race condition
✓ Missing test case
✓ This comment could be clearer
✓ Consider using...
✓ Consider using...
✓ Consider using...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem isn't that every comment is bad.&lt;/p&gt;

&lt;p&gt;Some might be useful.&lt;/p&gt;

&lt;p&gt;The problem is that the developer now has to perform another review:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Which of these comments should I care about?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's review fatigue.&lt;/p&gt;

&lt;p&gt;And it can happen surprisingly quickly.&lt;/p&gt;




&lt;h1&gt;
  
  
  There are four kinds of &lt;a href="http://www.codzee.io/" rel="noopener noreferrer"&gt;AI review noise&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;I've found it useful to think about review noise in four categories.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Wrong
&lt;/h2&gt;

&lt;p&gt;The reviewer identifies something that isn't actually a problem.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This condition can never be true.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Except it can.&lt;/p&gt;

&lt;p&gt;The reviewer simply misunderstood how the value reaches the function.&lt;/p&gt;

&lt;p&gt;This is the easiest type of noise to recognize.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Technically correct
&lt;/h2&gt;

&lt;p&gt;This one is more dangerous.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This function is 45 lines long.
Consider splitting it into smaller functions.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fine.&lt;/p&gt;

&lt;p&gt;Maybe that's a good idea.&lt;/p&gt;

&lt;p&gt;But if the PR is fixing a production incident and the function is intentionally kept together for transactional behavior, this isn't the problem the reviewer should be talking about.&lt;/p&gt;

&lt;p&gt;The comment isn't wrong.&lt;/p&gt;

&lt;p&gt;It just isn't useful right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Missing context
&lt;/h2&gt;

&lt;p&gt;This happens constantly.&lt;/p&gt;

&lt;p&gt;The reviewer sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (!user) {
    return;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Potentially unsafe null handling.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But the API intentionally permits anonymous users.&lt;/p&gt;

&lt;p&gt;The reviewer doesn't understand the business rule.&lt;/p&gt;

&lt;p&gt;The developer does.&lt;/p&gt;

&lt;p&gt;Now the developer has to explain the business rule to the AI reviewer.&lt;/p&gt;

&lt;p&gt;That's backwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Repetition
&lt;/h2&gt;

&lt;p&gt;This is the fastest way to turn useful tooling into background noise.&lt;/p&gt;

&lt;p&gt;The same type of comment appears over and over.&lt;/p&gt;

&lt;p&gt;Eventually developers recognize the pattern.&lt;/p&gt;

&lt;p&gt;They stop reading.&lt;/p&gt;

&lt;p&gt;And once they stop reading, the good comments get ignored too.&lt;/p&gt;




&lt;h1&gt;
  
  
  The worst metric for AI code review
&lt;/h1&gt;

&lt;p&gt;I don't care how many comments an AI reviewer generates.&lt;/p&gt;

&lt;p&gt;Seriously.&lt;/p&gt;

&lt;p&gt;Imagine two systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  System A
&lt;/h3&gt;

&lt;p&gt;100 comments.&lt;/p&gt;

&lt;p&gt;8 accepted.&lt;/p&gt;

&lt;h3&gt;
  
  
  System B
&lt;/h3&gt;

&lt;p&gt;15 comments.&lt;/p&gt;

&lt;p&gt;9 accepted.&lt;/p&gt;

&lt;p&gt;Which one would you rather have?&lt;/p&gt;

&lt;p&gt;Obviously B.&lt;/p&gt;

&lt;p&gt;But comment volume is seductive because it's easy to put on a dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PRs reviewed: 2,381

Issues detected: 14,921
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That sounds impressive.&lt;/p&gt;

&lt;p&gt;It doesn't tell you whether developers found those issues useful.&lt;/p&gt;

&lt;p&gt;I'd rather see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Findings: 1,200

Accepted: 780
Dismissed: 310
Duplicates: 70
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're measuring something interesting.&lt;/p&gt;




&lt;h1&gt;
  
  
  Try a review-noise audit
&lt;/h1&gt;

&lt;p&gt;If your team already uses AI code review, here's a simple experiment.&lt;/p&gt;

&lt;p&gt;Take the last 100 AI-generated review comments.&lt;/p&gt;

&lt;p&gt;Put each one into one of these buckets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A — Fixed
B — Discussed / investigated
C — Dismissed
D — Duplicate
E — Not actionable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then calculate the percentages.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fixed:              31%
Investigated:       18%
Dismissed:           27%
Duplicate:           12%
Not actionable:      12%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have a much more useful picture of your reviewer.&lt;/p&gt;

&lt;p&gt;The interesting number isn't “31% fixed.”&lt;/p&gt;

&lt;p&gt;It's everything else.&lt;/p&gt;

&lt;p&gt;Because every dismissed or low-value comment still consumed some amount of developer attention.&lt;/p&gt;




&lt;h1&gt;
  
  
  Then look at severity
&lt;/h1&gt;

&lt;p&gt;This is another place where AI reviewers can lose trust.&lt;/p&gt;

&lt;p&gt;Suppose a normal PR gets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5 critical issues
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You investigate them.&lt;/p&gt;

&lt;p&gt;Three are style suggestions.&lt;/p&gt;

&lt;p&gt;One is a minor maintainability concern.&lt;/p&gt;

&lt;p&gt;One is actually important.&lt;/p&gt;

&lt;p&gt;After a few PRs like that, what does “critical” mean to you?&lt;/p&gt;

&lt;p&gt;Probably not much.&lt;/p&gt;

&lt;p&gt;Severity needs a contract.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;h3&gt;
  
  
  Critical
&lt;/h3&gt;

&lt;p&gt;Could realistically cause:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;security compromise&lt;/li&gt;
&lt;li&gt;data loss&lt;/li&gt;
&lt;li&gt;major outage&lt;/li&gt;
&lt;li&gt;broken core functionality&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  High
&lt;/h3&gt;

&lt;p&gt;Meaningful production risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Medium
&lt;/h3&gt;

&lt;p&gt;Worth fixing but not urgent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Low
&lt;/h3&gt;

&lt;p&gt;Minor concern.&lt;/p&gt;

&lt;p&gt;And then there's another category:&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't comment
&lt;/h3&gt;

&lt;p&gt;If the reviewer isn't confident enough, it should stay quiet.&lt;/p&gt;




&lt;h1&gt;
  
  
  The uncomfortable lesson
&lt;/h1&gt;

&lt;p&gt;Developers don't need an AI reviewer that notices everything.&lt;/p&gt;

&lt;p&gt;They need one that knows what &lt;strong&gt;not&lt;/strong&gt; to mention.&lt;/p&gt;

&lt;p&gt;That's a very different optimization target.&lt;/p&gt;

&lt;p&gt;A human reviewer naturally filters.&lt;/p&gt;

&lt;p&gt;They might see 20 questionable things and comment on 2.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because they understand that every comment costs the author something.&lt;/p&gt;

&lt;p&gt;Time.&lt;/p&gt;

&lt;p&gt;Attention.&lt;/p&gt;

&lt;p&gt;Discussion.&lt;/p&gt;

&lt;p&gt;Potential context switching.&lt;/p&gt;

&lt;p&gt;AI systems don't naturally feel that cost.&lt;/p&gt;

&lt;p&gt;They need to be designed around it.&lt;/p&gt;




&lt;h1&gt;
  
  
  AI writing code makes this more important
&lt;/h1&gt;

&lt;p&gt;There's another reason this problem is getting bigger.&lt;/p&gt;

&lt;p&gt;AI coding agents can produce much larger changes much faster.&lt;/p&gt;

&lt;p&gt;A developer might ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Add OAuth login.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent might modify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;auth/
middleware/
routes/
database/
frontend/
tests/
configuration/
documentation/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now someone has to understand that entire change.&lt;/p&gt;

&lt;p&gt;The bottleneck has moved.&lt;/p&gt;

&lt;p&gt;We used to ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How can we write code faster?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we increasingly need to ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How can we verify more code without increasing cognitive load at the same rate?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a much harder engineering problem.&lt;/p&gt;




&lt;h1&gt;
  
  
  So what should an AI reviewer actually do?
&lt;/h1&gt;

&lt;p&gt;My ideal reviewer has five rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 1: Don't repeat deterministic tools
&lt;/h2&gt;

&lt;p&gt;If ESLint already catches it, don't produce another paragraph explaining it.&lt;/p&gt;

&lt;p&gt;If the compiler catches it, don't create an AI warning.&lt;/p&gt;

&lt;p&gt;AI should focus on things that require reasoning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 2: Understand the surrounding code
&lt;/h2&gt;

&lt;p&gt;A diff without context is often not enough.&lt;/p&gt;

&lt;p&gt;The reviewer should understand relevant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;functions&lt;/li&gt;
&lt;li&gt;tests&lt;/li&gt;
&lt;li&gt;interfaces&lt;/li&gt;
&lt;li&gt;configuration&lt;/li&gt;
&lt;li&gt;repository conventions&lt;/li&gt;
&lt;li&gt;PR intent&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rule 3: Prioritize risk
&lt;/h2&gt;

&lt;p&gt;A potential authentication vulnerability should not appear underneath 12 variable-naming suggestions.&lt;/p&gt;

&lt;p&gt;Rank findings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 4: Explain why the finding matters
&lt;/h2&gt;

&lt;p&gt;Don't say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This could cause a problem.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Say what could happen.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This query now executes once per customer because it sits inside the loop. At 10,000 customers, this turns the operation into an N+1 query pattern.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's actionable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 5: Be willing to say nothing
&lt;/h2&gt;

&lt;p&gt;This may be the most important rule.&lt;/p&gt;

&lt;p&gt;A review that returns:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“No high-confidence issues found.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;can be a successful review.&lt;/p&gt;

&lt;p&gt;Silence is better than noise.&lt;/p&gt;




&lt;h1&gt;
  
  
  Where Codzee fits
&lt;/h1&gt;

&lt;p&gt;This is the problem space where &lt;a href="http://www.codzee.io/" rel="noopener noreferrer"&gt;Codzee&lt;/a&gt; is interesting.&lt;/p&gt;

&lt;p&gt;The useful question isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Can we put AI on every pull request?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's relatively easy now.&lt;/p&gt;

&lt;p&gt;The harder question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Can we put AI on every pull request without making developers hate the review process?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That requires better prioritization, useful context, and a focus on actionable findings rather than maximizing comment volume.&lt;/p&gt;

&lt;p&gt;That's the direction AI-assisted code review needs to move.&lt;/p&gt;




&lt;h1&gt;
  
  
  A simple mental model
&lt;/h1&gt;

&lt;p&gt;Think about AI review like a security guard.&lt;/p&gt;

&lt;p&gt;A bad security guard stops everyone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;“Bag check.”

“Bag check.”

“Bag check.”

“Bag check.”

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eventually everyone stops paying attention.&lt;/p&gt;

&lt;p&gt;A good security guard doesn't stop everyone.&lt;/p&gt;

&lt;p&gt;They identify the situations that actually deserve attention.&lt;/p&gt;

&lt;p&gt;AI code review should work the same way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't maximize interruptions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maximize useful interruptions.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  The real success metric
&lt;/h1&gt;

&lt;p&gt;After a few months of using an AI reviewer, ask your team one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“When this reviewer leaves a comment, do you usually read it?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If everyone says yes, you've probably built trust.&lt;/p&gt;

&lt;p&gt;If people say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I usually just dismiss them.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You have a problem.&lt;/p&gt;

&lt;p&gt;And it's not necessarily model accuracy.&lt;/p&gt;

&lt;p&gt;It's relevance.&lt;/p&gt;

&lt;p&gt;The reviewer has become background noise.&lt;/p&gt;

&lt;p&gt;That's what I mean by &lt;strong&gt;earned irrelevance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The AI didn't necessarily fail because it couldn't find bugs.&lt;/p&gt;

&lt;p&gt;It failed because developers stopped believing that its comments were worth their attention.&lt;/p&gt;

&lt;p&gt;And in code review, attention is the resource that matters most.&lt;/p&gt;




&lt;h2&gt;
  
  
  What about your team?
&lt;/h2&gt;

&lt;p&gt;If you're already using an AI code reviewer, try the 100-comment audit.&lt;/p&gt;

&lt;p&gt;Take your last 100 comments.&lt;/p&gt;

&lt;p&gt;Classify them.&lt;/p&gt;

&lt;p&gt;Then see how many were actually useful.&lt;/p&gt;

&lt;p&gt;I'd be genuinely interested in what teams are finding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are AI reviewers reducing your review workload, or have they just created another thing you have to review?&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: This article discusses Codzee as an example of AI-assisted code-review tooling. The broader argument is about the design of AI review workflows, not a claim that any single tool solves the problem.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The AI Code Review Checklist I Actually Use Before Merging a PR</title>
      <dc:creator>Codzee.io</dc:creator>
      <pubDate>Wed, 19 Aug 2026 12:48:48 +0000</pubDate>
      <link>https://dev.to/codzee_io/the-ai-code-review-checklist-i-actually-use-before-merging-a-pr-1l9i</link>
      <guid>https://dev.to/codzee_io/the-ai-code-review-checklist-i-actually-use-before-merging-a-pr-1l9i</guid>
      <description>&lt;p&gt;I've been using &lt;a href="//www.codzee.io"&gt;AI &lt;/a&gt;coding tools heavily for a while now, and the thing that changed my workflow the most wasn't the code generation itself, it was building a review habit that actually catches the failure modes specific to generated code. AI output tends to look more finished than it is. The variable names are good, the structure is clean, the comments are confident. That polish is exactly what makes it easy to under-review.&lt;/p&gt;

&lt;p&gt;This is the checklist I actually run through, not the one I aspire to run through. Eight categories, each with a quick explanation of why it matters and a small example of the kind of thing that slips past a fast skim.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Correctness: does it do what was asked, not just something plausible
&lt;/h2&gt;

&lt;p&gt;AI models are very good at producing code that satisfies the letter of a prompt while missing the actual intent. If you ask for "a function that returns the most recent order for a user," you need to check what "most recent" means in the data. Sorted by creation date? Updated date? What if there are two orders with the same timestamp?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_most_recent_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works until two orders share a timestamp, and then the result is whichever one Python's sort happens to place last, which is not "most recent" in any meaningful sense. It's arbitrary. Nobody asked for arbitrary.&lt;/p&gt;

&lt;p&gt;Why it matters: correctness bugs from AI output are rarely syntax errors, they're semantic gaps between what was asked and what got built. You have to read the code against the requirement, not just against itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Edge cases: the inputs nobody put in the prompt
&lt;/h2&gt;

&lt;p&gt;If your prompt didn't mention empty lists, null values, negative numbers, or duplicate entries, the model probably didn't think about them either. It optimizes for the happy path described in the request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateAverage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass an empty array and you get &lt;code&gt;NaN&lt;/code&gt; silently propagating into whatever calls this next. No error, no crash, just a quietly wrong number working its way downstream.&lt;/p&gt;

&lt;p&gt;Why it matters: edge cases are where AI-generated code fails most often, because the model is pattern-matching against the common case in its training data, not reasoning about your specific data guarantees.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Security: don't assume the model thought about it unless you told it to
&lt;/h2&gt;

&lt;p&gt;If you didn't explicitly ask for input sanitization, parameterized queries, or auth checks, don't assume they're there. They often aren't, because the prompt didn't ask for them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM users WHERE email = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;
&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is straightforward SQL injection, and it's also one of the most common patterns I see generated when someone asks for "a function that looks up a user by email" without mentioning security at all.&lt;/p&gt;

&lt;p&gt;Why it matters: generated code defaults to the simplest version of a solution, and the simplest version is very often the insecure version. Treat every generated data access or input-handling function as unreviewed for security until you've explicitly checked it.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Performance: check the complexity, not just the output
&lt;/h2&gt;

&lt;p&gt;Generated code frequently reaches for the most obvious algorithm rather than the most efficient one, especially for anything involving lookups or nested iteration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_duplicates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;duplicates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;duplicates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;duplicates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;duplicates&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is O(n²) for a problem that's a few lines away from O(n) with a set or a counter. It'll pass every test on a small list and quietly become a bottleneck the moment someone runs it against production-sized data.&lt;/p&gt;

&lt;p&gt;Why it matters: small inputs during testing hide complexity problems that only show up at scale, and AI output doesn't come with a complexity analysis attached. You have to do that part yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Maintainability: will the next person understand this without you
&lt;/h2&gt;

&lt;p&gt;Generated code is often correct in isolation but disconnected from the conventions of the rest of your codebase. Different error handling style, different naming pattern, a slightly different way of structuring similar logic that already exists three files over.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# existing pattern in the codebase
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserNotFoundError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="c1"&gt;# what got generated for a new function
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;  &lt;span class="c1"&gt;# inconsistent with the rest of the codebase's error pattern
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why it matters: consistency is a maintainability feature. Code that solves the problem correctly but ignores established patterns adds friction for every future reader, even if it never causes a bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Tests: check what's actually being asserted, not just that tests exist
&lt;/h2&gt;

&lt;p&gt;Generated tests can create a false sense of safety. It's common to get tests that run without error but don't assert anything meaningful.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_process_payment&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test passes whether the payment succeeded, failed silently, or returned an empty error object. It confirms the function returned something, not that it did the right thing.&lt;/p&gt;

&lt;p&gt;Why it matters: a test suite with weak assertions is worse than no test suite in some ways, because it creates confidence that isn't backed by actual verification. Read the assertions, not just the test count.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Dependencies: verify the package actually exists and does what's claimed
&lt;/h2&gt;

&lt;p&gt;Models occasionally reference packages, methods, or APIs that don't exist, or that existed in an older version and have since changed. This is less common than it used to be but still worth a direct check, especially for less mainstream libraries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iteritems&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# removed in pandas 2.0, replaced by df.items()
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code like this can look completely reasonable to someone who hasn't kept up with a specific library's recent changes, and it'll fail immediately in CI or, worse, only in a slightly older pinned environment.&lt;/p&gt;

&lt;p&gt;Why it matters: a broken import or a deprecated method is an easy fix once caught, but it's an annoying one to debug blind if it ships and fails somewhere downstream instead of at review time.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Production behavior: what happens when a dependency it relies on goes down
&lt;/h2&gt;

&lt;p&gt;This is the category that's easiest to skip because it doesn't show up in normal testing at all. Does the code handle timeouts? Partial failures? What does logging look like when something goes wrong at 3am and someone's trying to debug it without you in the room?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOTIFICATION_API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No timeout, no retry, no handling of a non-200 response, and no logging if it fails. When the notification service has a bad five minutes, this function will hang or throw an unhandled exception with zero context about what actually happened.&lt;/p&gt;

&lt;p&gt;Why it matters: this is the category of bug that doesn't show up until it's already in production, because local development rarely simulates a flaky dependency. It's worth explicitly asking "what does this look like when the thing it depends on is unavailable" for anything that talks to an external service.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern across all eight
&lt;/h2&gt;

&lt;p&gt;Every one of these examples looks fine at a glance. That's not a coincidence, it's the actual challenge with reviewing generated code. The syntax is clean, the naming is sensible, the code reads as intentional. The gaps aren't in how the code looks, they're in what it doesn't account for, and you only catch that by asking specific questions of it rather than scanning for things that look wrong.&lt;/p&gt;

&lt;p&gt;Save this, print it, stick it next to your monitor, whatever works. The categories don't change much project to project, only the specifics do.&lt;/p&gt;

&lt;p&gt;What would you add to this list? Curious what's bitten people that isn't covered here.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>coding</category>
      <category>productivity</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Before You Merge AI-Generated Code, Ask These 12 Questions</title>
      <dc:creator>Codzee.io</dc:creator>
      <pubDate>Fri, 14 Aug 2026 12:21:07 +0000</pubDate>
      <link>https://dev.to/codzee_io/before-you-merge-ai-generated-code-ask-these-12-questions-14p3</link>
      <guid>https://dev.to/codzee_io/before-you-merge-ai-generated-code-ask-these-12-questions-14p3</guid>
      <description>&lt;p&gt;I've merged plenty of AI-generated code that was genuinely fine. I've also caught myself almost merging code that looked fine and wasn't, because it read like something a competent person wrote and my brain filled in the rest.&lt;/p&gt;

&lt;p&gt;Over the last year I've settled into a rough set of questions I run through before approving anything I didn't write line by line myself, generated or not. Here they are, in the order I actually ask them.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What problem is this code actually solving?
&lt;/h2&gt;

&lt;p&gt;It's easy to review whether code works and skip whether it solves the right thing. AI tends to answer the literal prompt, not the intent behind it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_active_users&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM users WHERE active = true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If "active" was supposed to mean "logged in within 30 days" and not a boolean flag that's rarely updated, this passes every test and still solves the wrong problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviewer tip:&lt;/strong&gt; Read the original ticket or request before reading the diff. Check the code against the intent, not just the literal ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Do I actually understand the implementation?
&lt;/h2&gt;

&lt;p&gt;Not "does it look reasonable," actually understand it, line by line, well enough to explain it to someone else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviewer tip:&lt;/strong&gt; Try to explain the function out loud in one sentence per major step. If you get stuck anywhere, that's the part you haven't actually reviewed yet, just skimmed.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What assumptions is it making?
&lt;/h2&gt;

&lt;p&gt;Every implementation bakes in assumptions about the shape of the data, the order things happen in, or what "normal" looks like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getLatestOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This assumes &lt;code&gt;orders&lt;/code&gt; is sorted chronologically and never empty. Neither assumption is stated anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviewer tip:&lt;/strong&gt; Ask "what does this assume about its inputs that isn't checked anywhere?" Write the answer down, literally, in the PR comment if it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. What happens with bad input?
&lt;/h2&gt;

&lt;p&gt;Bad input isn't an edge case, it's a certainty over a long enough timeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass it &lt;code&gt;"25"&lt;/code&gt; and it works. Pass it &lt;code&gt;"twenty-five"&lt;/code&gt;, &lt;code&gt;None&lt;/code&gt;, or &lt;code&gt;-5&lt;/code&gt; and you get a crash or a nonsensical value with no complaint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviewer tip:&lt;/strong&gt; Pick three inputs that would never appear in a demo but could plausibly appear in production: empty, wrong type, absurdly large. Trace through what actually happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. What happens when an external service fails?
&lt;/h2&gt;

&lt;p&gt;Generated code frequently assumes the network, database, or third-party API always responds successfully.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getExchangeRate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.example.com/rates/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No timeout, no handling for a non-200 response, no fallback. If that API is slow or down, this fails in whatever way &lt;code&gt;fetch&lt;/code&gt; and &lt;code&gt;.json()&lt;/code&gt; happen to fail, which may not be a clear error at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviewer tip:&lt;/strong&gt; For every external call, ask "what does the caller see if this times out or returns an error status?" If the answer is "an unhandled exception," that's worth a comment.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Are permissions actually enforced, not just checked?
&lt;/h2&gt;

&lt;p&gt;There's a difference between "there's an auth check" and "it's the right auth check."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirms someone is logged in. It never confirms they own or have access to &lt;em&gt;this&lt;/em&gt; document.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviewer tip:&lt;/strong&gt; For any endpoint touching a specific resource, ask "does this check the resource belongs to the requester, or just that the requester is logged in?"&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Is sensitive data exposed anywhere?
&lt;/h2&gt;

&lt;p&gt;Look at what actually goes into logs, error responses, and API payloads, not just what the happy path returns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fine in local development. In production this can leak file paths, query fragments, or internal structure to whoever triggers the error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviewer tip:&lt;/strong&gt; Grep the diff for &lt;code&gt;console.log&lt;/code&gt;, &lt;code&gt;print&lt;/code&gt;, and catch blocks. Check what they actually expose.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Is this more complex than the problem needs?
&lt;/h2&gt;

&lt;p&gt;Generated code sometimes over-engineers a simple problem with extra configuration, unnecessary abstraction layers, or generic solutions to specific problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviewer tip:&lt;/strong&gt; Ask "could this be half the length and still be correct?" If yes, that's worth pushing back on, complexity has an ongoing cost even when it's not technically wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Are the tests actually meaningful?
&lt;/h2&gt;

&lt;p&gt;Generated tests often confirm the code does what it does, not that it does what it should.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_apply_discount&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;apply_discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirms the arithmetic. It says nothing about a discount over 100%, a negative price, or invalid input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviewer tip:&lt;/strong&gt; For each test, ask "what wrong implementation would still pass this?" If you can think of one easily, the test isn't pinning down enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Does it fit existing conventions?
&lt;/h2&gt;

&lt;p&gt;Locally correct code can still be a long-term problem if it introduces a new pattern the codebase doesn't already use, a different error-handling style, a new HTTP client, a different logging approach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviewer tip:&lt;/strong&gt; Before approving, check one comparable file elsewhere in the codebase. If the patterns don't match, ask whether that's intentional.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. What happens under unusual load or concurrency?
&lt;/h2&gt;

&lt;p&gt;Code that's correct for one request at a time can break under concurrent access, especially anything involving shared state or caching.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fine single-threaded. Under concurrent requests this can lose increments, since read-then-write isn't atomic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviewer tip:&lt;/strong&gt; For anything touching shared state, ask "what happens if this runs twice at the exact same moment?"&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Will the next developer understand this without me?
&lt;/h2&gt;

&lt;p&gt;The final check. If someone opens this file in eight months with zero memory of this PR, can they figure out what it does and why from the code and comments alone?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviewer tip:&lt;/strong&gt; Read the diff as a stranger would, not as someone who already knows what it's supposed to do. If it doesn't hold up, add a comment now while the reasoning is still fresh.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy-paste checklist for your PR template
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## AI-Generated Code Review Checklist&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; [ ] Solves the actual requirement, not just a literal reading of it
&lt;span class="p"&gt;-&lt;/span&gt; [ ] I understand every part of this well enough to explain it
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Assumptions are identified and actually hold true
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Handles empty, invalid, and unexpected input
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Handles external service failures and timeouts
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Permissions checked against the specific resource, not just login state
&lt;span class="p"&gt;-&lt;/span&gt; [ ] No sensitive data leaked via logs, errors, or responses
&lt;span class="p"&gt;-&lt;/span&gt; [ ] No unnecessary complexity for the problem size
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Tests check behavior, not just current output
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Matches existing codebase conventions
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Considered behavior under concurrent access or load
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Understandable to a developer with no context on this PR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of this is exotic review practice. It's the same discipline good engineers apply to any code they didn't personally trace through, generated or not. The only thing that's changed is how often that situation comes up, and how easy it is to skip these questions when the first draft already looks like someone competent wrote it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>What should an AI code reviewer never be allowed to decide?</title>
      <dc:creator>Codzee.io</dc:creator>
      <pubDate>Thu, 13 Aug 2026 11:31:55 +0000</pubDate>
      <link>https://dev.to/codzee_io/what-should-an-ai-code-reviewer-never-be-allowed-to-decide-aem</link>
      <guid>https://dev.to/codzee_io/what-should-an-ai-code-reviewer-never-be-allowed-to-decide-aem</guid>
      <description>&lt;h1&gt;
  
  
  What should an AI code reviewer never be allowed to decide?
&lt;/h1&gt;

&lt;p&gt;Automated code analysis has gotten genuinely good at finding things. Unhandled exceptions, missing null checks, patterns that historically correlate with bugs, inconsistent formatting, code that duplicates logic sitting somewhere else in the repo. That part of the job, pattern detection across a large surface area, is exactly the kind of task a machine does faster and more consistently than a tired human scrolling through a 400-line diff at the end of the day.&lt;/p&gt;

&lt;p&gt;But there's a difference between finding a potential problem and deciding what the team should do about it, and I think that line is where a lot of the current conversation about automated review gets confused.&lt;/p&gt;

&lt;p&gt;The data backs up the caution here. In &lt;a href="//www.codzee.io"&gt;StackOverflow's &lt;/a&gt;2025 Developer Survey, adoption of AI tools kept climbing, with the majority of professional developers now using them regularly, yet trust in the accuracy of their output actually dropped compared to the year before. When asked what would still make them reach out to a human instead of relying on AI, the single most common answer developers gave was simply not trusting the AI's answer. That's not a story about a tool getting worse. It's a story about developers getting a clearer sense of where the tool's judgment should stop and theirs should start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding a problem vs deciding what to do about it
&lt;/h2&gt;

&lt;p&gt;Here's the distinction in practice. "This function doesn't handle a null input" is a finding. "This function should be rewritten to reject null inputs at the boundary instead of checking for them internally" is a decision, because it depends on how the rest of the system is structured, what the team's conventions are, and whether that boundary even exists yet. A tool can flag the first. Only a human with context can respond to the second responsibly.&lt;/p&gt;

&lt;p&gt;That distinction matters more once you list out the kinds of decisions that come up constantly in real review, and notice how few of them are actually technical questions in disguise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product requirements.&lt;/strong&gt; Whether a feature does what the business actually needs isn't something code analysis can evaluate. It can check if the code matches a spec. It has no way of knowing if the spec itself is right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Business priorities.&lt;/strong&gt; Whether this PR should ship this week or wait for a more complete &lt;a href="//www.codzee.io"&gt;solution &lt;/a&gt;next sprint depends on revenue timing, customer commitments, and competitive pressure, none of which live in the diff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural tradeoffs.&lt;/strong&gt; Microservice or monolith, synchronous or event-driven, these choices ripple across a system for years and depend on team size, deployment maturity, and organizational appetite for operational complexity. A tool can tell you a pattern is unusual. It can't tell you it's wrong for your context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Acceptable technical debt.&lt;/strong&gt; A shortcut taken deliberately, with a clear owner and a plan to revisit it, is a completely different thing from the same shortcut taken carelessly. The code looks identical either way. The judgment about whether it's acceptable lives entirely outside the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User experience.&lt;/strong&gt; Whether an extra confirmation step protects users or just adds friction is a design and empathy question, not a static analysis question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security risk tolerance.&lt;/strong&gt; A tool can flag that input isn't validated. Whether that gap is acceptable for an internal admin tool used by three trusted employees, versus unacceptable for a public signup form, requires knowing who's exposed and what the actual threat model is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance tradeoffs.&lt;/strong&gt; Whether shaving fifty milliseconds off a response is worth the added code complexity depends entirely on whether that fifty milliseconds affects anyone's experience at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Organizational conventions.&lt;/strong&gt; Naming patterns, folder structure, preferred libraries. These exist because a team agreed on them, not because one option is objectively correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whether a feature should exist at all.&lt;/strong&gt; No amount of analysis on the code tells you if building it was the right call in the first place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whether a shortcut is strategically acceptable.&lt;/strong&gt; Sometimes shipping something slightly rough now, on purpose, is the correct business decision. That's a judgment about timing and risk appetite, not about the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete example
&lt;/h2&gt;

&lt;p&gt;Picture a PR that adds a new caching layer in front of a slow database query. Automated analysis can correctly flag that the cache has no invalidation strategy, and that's a genuinely useful finding, the kind of thing that's easy to miss under deadline pressure. What it cannot tell you is whether stale data for up to five minutes is a real problem for this particular feature, or a complete non-issue because the underlying data barely changes. That call depends on what the data represents and who's looking at it, and only someone with product and business context can make it responsibly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this balance actually works
&lt;/h2&gt;

&lt;p&gt;None of this is an argument against using automated analysis in review. It's genuinely useful for exactly what it's built for, catching the categories of mistakes that are easy to miss under time pressure, at a scale no human reviewer can match across a large codebase. The 2025 Sonar developer survey found that a large share of committed code now involves some form of AI assistance, and developers report reviewing and validating it more carefully as a result rather than less, which suggests the healthy pattern here is already forming naturally on a lot of teams.&lt;/p&gt;

&lt;p&gt;The honest position is that these tools are excellent at expanding what gets checked, and completely unqualified to decide what should happen once something is found. Engineering judgment, the part that weighs tradeoffs, understands business context, and takes responsibility for the outcome, has to stay human.&lt;/p&gt;

&lt;p&gt;I write about this kind of review culture regularly over at &lt;a href="https://www.codzee.io" rel="noopener noreferrer"&gt;www.codzee.io&lt;/a&gt;, mostly because it's a distinction I think gets lost in a lot of the current hype cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Eight principles for responsible AI-assisted code review
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Automated findings are a starting point for discussion, not a verdict.&lt;/li&gt;
&lt;li&gt;A flagged issue always needs a human owner to decide what happens next.&lt;/li&gt;
&lt;li&gt;Never let a tool's confidence score substitute for a reviewer's understanding of context.&lt;/li&gt;
&lt;li&gt;Business, product, and architectural decisions stay with people who carry the consequences.&lt;/li&gt;
&lt;li&gt;Use automation to widen coverage, not to replace judgment on what matters most.&lt;/li&gt;
&lt;li&gt;Document the reasoning behind decisions, not just the fact that a tool flagged something.&lt;/li&gt;
&lt;li&gt;Treat every automated suggestion as a hypothesis worth checking, not a fact worth trusting blindly.&lt;/li&gt;
&lt;li&gt;If no human could explain why a decision was made, the decision wasn't actually made yet.&lt;/li&gt;
&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>I Asked 10 Developers What Makes Them Reject a Pull Request</title>
      <dc:creator>Codzee.io</dc:creator>
      <pubDate>Wed, 12 Aug 2026 12:10:36 +0000</pubDate>
      <link>https://dev.to/codzee_io/i-asked-10-developers-what-makes-them-reject-a-pull-request-7p0</link>
      <guid>https://dev.to/codzee_io/i-asked-10-developers-what-makes-them-reject-a-pull-request-7p0</guid>
      <description>&lt;p&gt;A quick note before this starts: I didn't actually run ten interviews for this post. What follows is a synthesis — patterns I've seen show up over and over in my own review comments, in other people's, and in the general "why did you block this" conversations that happen on pretty much every team that takes code review seriously. If you've been reviewing code for more than a year, I'd bet most of this is going to feel familiar rather than new. That's kind of the point. These aren't ten hot takes. They're ten things that keep independently showing up across very different teams, which probably means they're just true.&lt;/p&gt;

&lt;p&gt;I've grouped them into ten reasons, each with a small example and, more importantly, a note on how to raise the issue without it turning into a fight or a demoralized author. Because the "what" of a rejection is usually easy. The "how you say it" is where reviews go well or badly.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Broken Behavior
&lt;/h2&gt;

&lt;p&gt;This is the obvious one, but it's worth stating plainly: if the code doesn't do what it claims to do, nothing else matters yet. Style, naming, architecture — none of it is relevant if the function returns the wrong value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isEligibleForDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the actual business rule is "65 and older," this excludes someone who just turned 65. Small, easy to miss, and exactly the kind of thing that survives a glance because the code &lt;em&gt;reads&lt;/em&gt; fine — it's fluent, grammatical, wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to raise it constructively:&lt;/strong&gt; State the discrepancy, not the failure. "I think this excludes someone exactly at 65 — should the comparison be &lt;code&gt;&amp;gt;=&lt;/code&gt;?" is a question, not an accusation, and it gives the author room to say "actually that's intentional" if there's context you're missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Security Problems
&lt;/h2&gt;

&lt;p&gt;Security issues get treated differently from ordinary bugs because the cost of being wrong isn't "annoying," it's "someone's data leaked." That difference in stakes is why these tend to be non-negotiable even when they're inconvenient to fix right before a release.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM users WHERE email = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;String-interpolated SQL is the classic example, but the pattern shows up everywhere — unescaped output going into HTML, secrets logged in plaintext, auth checks that verify identity but not permission.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to raise it constructively:&lt;/strong&gt; Don't soften this one into vague language. "Nit: might want to look at this" undersells a real risk. Be direct about &lt;em&gt;why&lt;/em&gt; it matters without being alarmist: "This is vulnerable to SQL injection since &lt;code&gt;email&lt;/code&gt; comes straight from the request — can we use a parameterized query here?" Directness isn't the same as harshness.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. No Tests for Risky Changes
&lt;/h2&gt;

&lt;p&gt;Not every change needs a wall of new tests. But changes that touch money, permissions, data deletion, or anything hard to undo are a different category, and "it worked when I tried it" isn't the same as "it's covered."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refund_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refunded_amount&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No check that &lt;code&gt;amount&lt;/code&gt; doesn't exceed the order total, no test verifying the accumulation logic, no test for calling this twice. This is exactly the kind of function where a bug doesn't show up as a crash — it shows up as a quiet accounting discrepancy three weeks later that someone in finance has to chase down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to raise it constructively:&lt;/strong&gt; Explain the &lt;em&gt;category&lt;/em&gt; of risk rather than just asking for "more tests," which can feel like busywork. "Since this touches refund amounts directly, could we add a test for double-refunding and for a refund larger than the order total? Mostly want to make sure double-submission doesn't double-refund." That gives the author a concrete, reasoned target instead of an open-ended homework assignment.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Unclear Naming
&lt;/h2&gt;

&lt;p&gt;This one is easy to dismiss as bikeshedding, and sometimes it is. But naming is usually a proxy for something else — either the author isn't totally sure what the variable represents, or the code is doing something slightly different from what its name &lt;a href="https://codzee.io/" rel="noopener noreferrer"&gt;implies&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is &lt;code&gt;data&lt;/code&gt;? An order? A user? A raw API response? A parsed object? The name tells you nothing, and six months from now, "what does &lt;code&gt;data&lt;/code&gt; actually contain" becomes an archaeology project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to raise it constructively:&lt;/strong&gt; Ask instead of dictating. "What does &lt;code&gt;data&lt;/code&gt; represent here — maybe &lt;code&gt;pending_order&lt;/code&gt; or similar would make the next line clearer?" Sometimes the act of answering that question makes the author realize the variable is doing two different jobs, which is a more valuable outcome than just picking a better name.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Excessive Complexity
&lt;/h2&gt;

&lt;p&gt;Code that's technically correct but takes five reads to understand is still a liability — it's just a slower one. Complexity tends to accumulate gradually, one small conditional at a time, until a function that started simple is six levels of nested logic deep and nobody remembers why.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refunded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cancelled_refunded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refundPending&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cancelled_refund_pending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cancelled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shipped&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delivered&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;delivered&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shipped&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paid&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every branch here is individually reasonable. Together, they're hard to hold in your head at once, and that's the actual cost — not that any single line is wrong, but that verifying correctness requires simulating every path manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to raise it constructively:&lt;/strong&gt; Point at the &lt;em&gt;shape&lt;/em&gt;, not the person's competence. "This has a lot of nested branches — would a lookup table or early returns make the cases easier to scan?" Offering a concrete alternative (rather than just "this is too complex") turns the comment into something actionable instead of a vague judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Duplicated Logic
&lt;/h2&gt;

&lt;p&gt;A little duplication is often fine, even healthy — premature abstraction can be worse than a few repeated lines. But when the &lt;em&gt;same rule&lt;/em&gt; is implemented in more than one place, you've created a situation where fixing a bug requires remembering every location it was copied to, and someone always forgets one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# in billing.py
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^[^@]+@[^@]+\.[^@]+$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# in signup.py, written by someone else, months later
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bad email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two different validation rules for the same concept, drifting further apart every time either one gets touched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to raise it constructively:&lt;/strong&gt; Name the pattern rather than just the instance. "I think this is the third place we validate emails, and they're not all doing the same check — might be worth pulling into a shared function at some point, doesn't need to block this PR." Flagging it without demanding an immediate refactor respects the scope of what the author was actually trying to ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Poor Error Handling
&lt;/h2&gt;

&lt;p&gt;The failure path is where a lot of real bugs live, precisely because it's the path least exercised during normal development. Code that handles success gracefully and failure carelessly tends to look complete right up until it isn't.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;payment failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payment failed, the user probably still thinks their order went through, and the only record of what happened is a console log nobody's watching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to raise it constructively:&lt;/strong&gt; Ask what the user or caller experiences on failure, not just what the code does. "If this catch fires, what does the customer see? Right now I think they'd get a success response — should we surface the failure back to them?" Grounding the comment in the actual downstream experience makes the stakes concrete instead of abstract.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Unexpected Side Effects
&lt;/h2&gt;

&lt;p&gt;A function whose name promises one thing but does another is a specific, sneaky kind of bug, because it's invisible at every call site — the code &lt;em&gt;reads&lt;/em&gt; correct everywhere it's used, and the mismatch only becomes apparent when something downstream breaks for reasons nobody can trace back to this function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user_display_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A function that looks like a pure getter is quietly writing to the database on every call. Call it in a loop to render a list of a hundred names, and you've just issued a hundred writes nobody intended.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to raise it constructively:&lt;/strong&gt; Point out the mismatch between name and behavior specifically. "This reads like a pure getter but it's also updating &lt;code&gt;last_seen&lt;/code&gt; — is that intentional? If so, might be worth splitting the write out or renaming so it's obvious from the call site." The goal is making the side effect visible, not implying the author was being sneaky — usually it just accreted there over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Architectural Problems
&lt;/h2&gt;

&lt;p&gt;Sometimes a change is locally correct and still a problem, because of where it lives rather than what it does. A new responsibility bolted onto an existing module, a new dependency pointing the wrong direction, a boundary quietly crossed — these are the hardest comments to make well, because they're rarely about the diff itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# inside the OrderService
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_slack_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SLACK_WEBHOOK_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;New order &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing here is "wrong" in isolation. But now &lt;code&gt;OrderService&lt;/code&gt; — which should own order logic — also owns notification formatting and an external HTTP dependency, and every future change to how notifications work has to route through a class that was never meant to care about them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to raise it constructively:&lt;/strong&gt; These comments land best when they're explicitly framed as bigger-than-this-PR, so the author doesn't feel blocked on solving something out of scope. "This works, but I think notification logic living inside &lt;code&gt;OrderService&lt;/code&gt; is going to get tangled as we add more notification types — worth a separate &lt;code&gt;NotificationService&lt;/code&gt; down the line? Not blocking this PR on it." Naming the concern without demanding it be resolved right now respects both the code and the person's afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Code That Future Developers Cannot Understand
&lt;/h2&gt;

&lt;p&gt;This is the hardest one to pin down because it's not really a rule — it's a judgment call about whether someone with less context than the author, arriving in a year, can safely modify this code without breaking something they can't see.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every one of these numbers means something. None of that meaning is written down anywhere. Whoever touches this function next either has to reverse-engineer the business rule from the arithmetic, or — more likely — just leaves it alone forever because nobody wants to be the one who breaks a formula they don't understand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to raise it constructively:&lt;/strong&gt; Ask the author to explain it in the comment thread, and then ask if that explanation could live in the code instead of in the PR discussion. "Could you walk me through what &lt;code&gt;0.85&lt;/code&gt; and the &lt;code&gt;z &amp;gt; 3&lt;/code&gt; threshold represent? If it's a business rule, I think it'd help future-us to have that as a comment or named constant." Often the author &lt;em&gt;does&lt;/em&gt; know exactly what it means — the review is really just prompting them to write it down somewhere more permanent than their own memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rejecting Code Without Discouraging the Developer
&lt;/h2&gt;

&lt;p&gt;All ten of the reasons above can be delivered in a way that either helps someone grow or makes them dread opening their next PR. The difference usually comes down to a few habits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separate the code from the person.&lt;/strong&gt; "This has a bug" and "you wrote a bug" describe the same fact but land completely differently. The first is about the artifact. The second, even unintentionally, is about the author's competence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distinguish blocking issues from everything else.&lt;/strong&gt; Not every comment needs to hold up a merge, and treating a naming nit with the same tone as a security hole trains people to either panic at every comment or ignore all of them. Say plainly when something isn't blocking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ask before asserting, when there's room to be wrong.&lt;/strong&gt; A reviewer often doesn't have the full context the author does. "Why did this end up this way?" invites an explanation you might be missing. "This should be done differently" doesn't leave room for one, even when you're right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explain the why, not just the what.&lt;/strong&gt; "Move this to a separate function" is an instruction. "This function is doing three things, which makes it hard to test them independently — could we split it up?" is a reason, and reasons generalize to the next PR in a way that instructions don't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Say something positive when it's actually true.&lt;/strong&gt; Not as a cushioning tactic, but because &lt;a href="https://codzee.io/" rel="noopener noreferrer"&gt;reviews &lt;/a&gt;that are 100% critique start to feel like a performance review nobody asked for, and people stop putting effort into approaches worth praising because the effort never gets acknowledged.&lt;/p&gt;

&lt;p&gt;None of this means lowering the bar. A PR with a real security hole should still get blocked, clearly and without hedging. The point isn't to be gentler about &lt;em&gt;what&lt;/em&gt; gets rejected — it's to be more deliberate about &lt;em&gt;how&lt;/em&gt;, so that the person on the other end of the review comes away having learned something, instead of just having been told no.&lt;/p&gt;

&lt;p&gt;The reasons a PR gets rejected are, in the end, pretty consistent across teams and codebases. What varies enormously is whether the developer who wrote it feels like the review made their code better, or just made their afternoon worse. That difference is almost entirely in the delivery, not the content.&lt;/p&gt;

</description>
      <category>git</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>7 Things I Look For Before I Approve a Pull Request</title>
      <dc:creator>Codzee.io</dc:creator>
      <pubDate>Tue, 11 Aug 2026 10:54:13 +0000</pubDate>
      <link>https://dev.to/codzee_io/7-things-i-look-for-before-i-approve-a-pull-request-12jl</link>
      <guid>https://dev.to/codzee_io/7-things-i-look-for-before-i-approve-a-pull-request-12jl</guid>
      <description>&lt;h1&gt;
  
  
  7 Things I Look For Before I Approve a &lt;a href="https://codzee.io/" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Code review is one of those skills nobody really teaches you. You get thrown into it, you start by nitpicking variable names, and eventually — usually after shipping a bug that a five-second look would have caught — you develop an actual system.&lt;/p&gt;

&lt;p&gt;This is the system I use. It's not exhaustive and it's not fancy. It's just seven questions I ask myself on every PR, roughly in this order, before I click approve. I've included real code examples for each one — a bad version, a better version, and what I'm actually looking for as a reviewer.&lt;/p&gt;

&lt;p&gt;Let's get into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Does the code actually solve the problem?
&lt;/h2&gt;

&lt;p&gt;This sounds obvious, but it's the check people skip most often, because it's tempting to jump straight into reading syntax instead of first asking "does this match the ticket?"&lt;/p&gt;

&lt;p&gt;I read the PR description and the linked issue &lt;em&gt;before&lt;/em&gt; I read a single line of code. Then I ask: if I ran this exact scenario, would it produce the right outcome? Not "does this look like reasonable code" — does it solve the actual problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad version&lt;/strong&gt; — closes the ticket "filter out inactive users" but silently changes behavior elsewhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;guest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ticket asked for inactive users to be filtered out. This PR also drops guests — a requirement nobody asked for, that might break another screen relying on guest visibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Improved version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getActiveUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scoped exactly to what was requested. If guest filtering is genuinely needed, it belongs in its own PR with its own ticket and its own tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to notice as a reviewer:&lt;/strong&gt; scope creep disguised as a fix. Extra changes "while I was in there" are one of the most common sources of surprise regressions. If the PR does more than the ticket asked for, ask why, out loud, in the comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Is the code understandable?
&lt;/h2&gt;

&lt;p&gt;Working code and readable code are not the same thing. I try to read each function the way I'd read it cold, six months from now, with zero memory of writing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have no idea what this does without tracing through it. What's &lt;code&gt;d&lt;/code&gt;? What's index &lt;code&gt;2&lt;/code&gt;? Why &lt;code&gt;1.1&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Improved version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_discount_to_high_value_orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;discounted_orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;discounted_orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;discounted_orders&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same logic, but now I can understand the function's purpose from its name and its variables, without running it in my head.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to notice as a reviewer:&lt;/strong&gt; if you have to open a second tab, trace three function calls, or ask the author "wait, what does this do?" in a comment, that's a readability problem — not a you problem. Flag it. Naming and structure are load-bearing, not cosmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What happens when something goes wrong?
&lt;/h2&gt;

&lt;p&gt;Most demos work. The question is what the code does when the network drops, the API returns a 500, or a file doesn't exist. I specifically look for what happens on the unhappy path, because that's the path nobody tests manually before opening the PR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchUserProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No handling for a failed request, a non-200 response, or malformed JSON. One flaky network call and this throws an unhandled exception somewhere upstream, with a stack trace that tells the user nothing useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Improved version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchUserProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Network error fetching user &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to fetch user &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now failures are explicit, informative, and distinguishable — a caller can tell the difference between "the network died" and "the server said no."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to notice as a reviewer:&lt;/strong&gt; any &lt;code&gt;await&lt;/code&gt;, file read, external call, or parse step with no corresponding failure path. Ask: "what does the user see if this line throws?" If the honest answer is "a blank screen" or "an unhandled promise rejection in the console," that's not ready to merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Are edge cases handled?
&lt;/h2&gt;

&lt;p&gt;This is different from error handling. Edge cases are inputs that are technically valid but easy to forget: empty lists, zero, negative numbers, duplicate entries, the very first or very last item, extremely long strings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAverageScore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass in an empty array and you get &lt;code&gt;NaN&lt;/code&gt;, silently, with no error and no signal that anything went wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Improved version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAverageScore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// no scores yet — caller decides how to display this&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the empty case is a deliberate decision instead of an accident, and it's documented right there in the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to notice as a reviewer:&lt;/strong&gt; try to mentally run the function on an empty input, a single-item input, and a huge input. If the author clearly only ever tested with "3-5 typical items," that's usually where the bugs are hiding. Ask specifically: "what happens with zero items?" — it's a cheap question that catches a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Is the code secure?
&lt;/h2&gt;

&lt;p&gt;You don't need to be a security engineer to catch the common stuff. Most security bugs in day-to-day PRs aren't exotic — they're unsanitized input, secrets in code, or overly trusting data from outside the system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM products WHERE name = '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Classic SQL injection — anything the user types goes straight into the query string.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Improved version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT * FROM products WHERE name = ?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Search failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parameterized queries mean user input is data, never executable SQL, no matter what someone types into the search box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to notice as a reviewer:&lt;/strong&gt; any spot where user input touches a query, a shell command, a file path, or gets rendered directly into HTML. Also scan for hardcoded API keys, tokens, or passwords — even in a comment, even "temporarily." And check whether data from external APIs is validated before being trusted, the same way you'd validate user input.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Are the tests meaningful?
&lt;/h2&gt;

&lt;p&gt;A PR with 100% coverage can still have useless tests. I look at whether the tests actually verify behavior, or whether they just exercise the code without asserting anything meaningful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calculates discount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;applyDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeDefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test passes no matter what &lt;code&gt;applyDiscount&lt;/code&gt; returns, as long as it returns &lt;em&gt;something&lt;/em&gt;. It would still pass if the function returned &lt;code&gt;-999&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Improved version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;applies a 10% discount correctly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;applyDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns original price when discount is zero&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;applyDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;throws on a negative discount rate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;applyDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each test verifies a specific, checkable outcome — including an edge case and an invalid input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to notice as a reviewer:&lt;/strong&gt; look for assertions like &lt;code&gt;toBeDefined()&lt;/code&gt;, &lt;code&gt;toBeTruthy()&lt;/code&gt;, or no assertion at all — these are common signs of a test written to satisfy a coverage number rather than to catch a regression. Also check whether the tests cover the failure paths and edge cases from sections 3 and 4 above, not just the happy path. A test suite that only tests success is only half a test suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Will another developer understand this six months from now?
&lt;/h2&gt;

&lt;p&gt;This is the catch-all question, and I ask it last because by this point I've usually already spotted the answer. It's about intent, not syntax: will someone (possibly you) be able to figure out &lt;em&gt;why&lt;/em&gt; this code exists, not just what it does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// fix&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is type &lt;code&gt;3&lt;/code&gt;? Why &lt;code&gt;0.15&lt;/code&gt;? Six months from now, this is an archaeology project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Improved version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;USER_TYPE_ENTERPRISE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ENTERPRISE_DISCOUNT_RATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Enterprise customers get a 15% discount per the Q3 pricing agreement (JIRA-4821)&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;USER_TYPE_ENTERPRISE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ENTERPRISE_DISCOUNT_RATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Named constants explain &lt;em&gt;what&lt;/em&gt;, the comment explains &lt;em&gt;why&lt;/em&gt;, and the ticket reference gives anyone a way to dig deeper if they need to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to notice as a reviewer:&lt;/strong&gt; magic numbers, cryptic comments like &lt;code&gt;// fix&lt;/code&gt; or &lt;code&gt;// hack&lt;/code&gt;, and any logic that encodes a business rule without saying where that rule came from. If you, the reviewer, have to ask "why does this exist?" in the PR comments, future-you (or future-someone) will have to ask the same question with nobody around to answer it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pull Request Checklist
&lt;/h2&gt;

&lt;p&gt;Here's the condensed version I actually use, PR by PR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Solves the problem&lt;/strong&gt; — matches the ticket, no unrelated scope creep&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Readable&lt;/strong&gt; — I can follow the logic without re-reading it three times&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Handles failure&lt;/strong&gt; — network errors, bad responses, and exceptions are caught and explicit&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Handles edge cases&lt;/strong&gt; — empty input, zero, single item, duplicates, extremes&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Secure by default&lt;/strong&gt; — no raw string interpolation into queries/commands, no hardcoded secrets, external input is validated&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Tests actually assert something&lt;/strong&gt; — happy path, at least one edge case, at least one failure case&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Understandable in six months&lt;/strong&gt; — no unexplained magic numbers, comments explain &lt;em&gt;why&lt;/em&gt; not &lt;em&gt;what&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these checks take more than a minute or two individually. Together, they take maybe ten to fifteen minutes on a normal-sized PR — and they catch the vast majority of the bugs and headaches that would otherwise surface in production, at 2am, with much less context than you have right now.&lt;/p&gt;

&lt;p&gt;Review is cheap. Production incidents aren't. That trade-off is really the whole point of this checklist.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
