Code Reviews — Best Practices for Development Teams
In this tutorial, you'll learn about Code Reviews. We cover key concepts, practical examples, and best practices.
Code reviews are a disciplined practice where developers examine each other's code changes to catch defects, enforce standards, and transfer knowledge — serving as both a quality gate and a learning accelerator for engineering teams.
What You'll Learn
- The difference between lightweight review and formal inspection
- How to write reviewable code and effective review comments
- Optimal review scope, speed, and team processes
- Tooling and metrics for measuring review effectiveness
Why It Matters
A single undetected bug in a review can cost 10–100x more if discovered in production. Code reviews are the cheapest defect-detection method available — they find issues within minutes of the code being written, before tests run, before staging deploy, before users see it. They also spread domain knowledge, reduce bus factor, and create shared code ownership.
Real-World Use
During a code review at Durga Antivirus Pro, a reviewer noticed that a new file-scanning module used os.system() to invoke an external binary. The review comment pointed out the shell-injection risk and suggested subprocess.run() with a list argument instead. The fix took five minutes. Without that review, the module would have shipped with a remote code execution vulnerability.
What Makes a Good Code Review?
Code reviews sit on a spectrum from lightweight (quick pull request check) to formal (Fagan inspection with multiple roles and metrics). Most teams should aim for the middle — structured enough to catch real defects, lightweight enough to ship daily.
| Aspect | Lightweight Review | Formal Inspection |
|---|---|---|
| Participants | 1–2 reviewers | Moderator, author, reviewers, scribe |
| Preparation | Read diff in browser | Printed materials, checklist |
| Meeting | None (async) | Scheduled session |
| Outcome | Approve / Request changes | Sign-off with defect log |
| When to use | Daily PRs, routine changes | Critical modules, regulatory compliance |
How to Write Reviewable Code
The author's job is to make the review easy. A reviewer who struggles to understand your changes will miss bugs.
Keep changes small — A review of 200 lines finds more defects per line than a review of 1000 lines. Research shows optimal review size is 200–400 lines of changed code.
Write good commit messages — Explain why the change exists, not just what changed. This gives reviewers context to evaluate alternatives.
Separate refactoring from feature work — A PR that renames variables AND adds a new endpoint is impossible to review. Do one thing per pull request.
Add tests in the same PR — Reviewers should see tests alongside the code they cover. If tests are missing, the reviewer cannot verify correctness.
# BAD: hard to review — mixed concerns, no tests shown here
def process(data):
# refactor: rename variable
x = data.get("user_input")
# new feature: add validation
if len(x) > 0:
import os
os.system(f"echo {x}") # security issue!
# GOOD: reviewable — one concern, safe code
import subprocess
def process(data):
user_input = data.get("user_input", "")
if not user_input:
return
subprocess.run(["echo", user_input], check=True)
def test_process():
process({"user_input": "hello"})
# No output to assert — just verifying no crash
assert True
How to Write Good Review Comments
Review comments should be clear, specific, and respectful. The goal is to improve the code, not to win an argument.
Ask questions instead of giving commands — "What happens if the list is empty?" works better than "You forgot to handle the empty case." Questions invite discussion; commands trigger defensiveness.
Explain the why — "Using pathlib.Path instead of os.path.join helps with cross-platform path handling" is more useful than "Use pathlib instead."
Separate nitpicks from blockers — Prefix non-critical suggestions with "nit:" so the author knows they can ignore them. Reserve blocking comments for correctness, security, and design issues.
Praise good code — When you see a clever solution or a well-named function, say so. Positive comments encourage the behavior you want to see more of.
# A code review example showing the comment flow
def parse_config(filepath):
with open(filepath) as f:
raw = f.read()
# Reviewer: "What if this file is empty? An empty string would
# pass through json.loads without error but return None.
# Should we validate the content has at least one key?"
import json
return json.loads(raw)
# After the review, the author adds validation:
def parse_config(filepath):
with open(filepath) as f:
raw = f.read()
if not raw.strip():
raise ValueError("Config file is empty")
import json
data = json.loads(raw)
if not isinstance(data, dict) or not data:
raise ValueError("Config must be a non-empty JSON object")
return data
print(parse_config("/tmp/config.json"))
Expected output (assuming config.json contains {"debug": true}):
{'debug': True}
Optimal Review Speed
Speed matters more than most teams realize. A review that sits for three days creates context-switching overhead, merge conflicts, and delayed feedback loops.
| Review Time | Effect on Team |
|---|---|
| < 4 hours | Fast feedback, high velocity, minimal context switching |
| 4–24 hours | Acceptable — most teams target same-day reviews |
| 1–3 days | Noticeable drag — authors start working around the queue |
| 1+ weeks | Critical — blocks shipping, frustrates the team |
Best practice: respond within one business day. Approve small, obvious changes immediately. Schedule time for larger reviews.
Code Review Checklist
A shared checklist ensures consistent coverage across all reviews:
Correctness
- Does the code do what the requirements say?
- Are there missing edge cases (null, empty, overflow, timeout)?
- Do the tests cover the change adequately?
Security
- Are there any SQL injection, XSS, or command injection risks?
- Are secrets and credentials properly handled?
- Is input validated at trust boundaries?
Maintainability
- Are functions, classes, and variables named clearly?
- Is the code DRY (not duplicating existing logic)?
- Are complex sections documented with a brief comment?
Performance
- Are there N+1 queries or unnecessary loops?
- Is there any obvious resource leak (file handles, connections)?
# Example: automated code review checklist as a CI check
import ast
import sys
class ReviewChecklist(ast.NodeVisitor):
def __init__(self):
self.issues = []
def visit_Call(self, node):
if isinstance(node.func, ast.Attribute):
if node.func.attr in ("system", "popen"):
self.issues.append(
f"Line {node.lineno}: Avoid os.system/popen — use subprocess"
)
if isinstance(node.func, ast.Name):
if node.func.id == "eval":
self.issues.append(
f"Line {node.lineno}: Avoid eval() — security risk"
)
code = """
import os
os.system('ls -la')
result = eval('2 + 2')
"""
tree = ast.parse(code)
checker = ReviewChecklist()
checker.visit(tree)
for issue in checker.issues:
print(f"ISSUE: {issue}")
if checker.issues:
sys.exit(1)
else:
print("No issues found.")
Expected output:
ISSUE: Line 3: Avoid os.system/popen — use subprocess
ISSUE: Line 4: Avoid eval() — security risk
Measuring Review Effectiveness
Teams that measure their review process improve it. Key metrics:
| Metric | Target | Why |
|---|---|---|
| Review turnaround time | < 24 hours | Prevents blocking the author |
| Defects found per review | 0.5–2.0 | Too low = reviews are shallow; too high = code quality is poor |
| Review coverage (% of PRs reviewed) | 100% | Every change needs at least one review |
| Comments per review | 2–8 | Fewer = rubber-stamping; more = scope may be too large |
# review_metrics.py — track review performance over time
from collections import defaultdict
def compute_review_metrics(reviews):
total = len(reviews)
if total == 0:
return {}
avg_comments = sum(r["comments"] for r in reviews) / total
avg_defects = sum(r["defects_found"] for r in reviews) / total
avg_turnaround = sum(r["hours_to_review"] for r in reviews) / total
return {
"total_reviews": total,
"avg_comments": round(avg_comments, 1),
"avg_defects_found": round(avg_defects, 1),
"avg_turnaround_hours": round(avg_turnaround, 1),
}
sample = [
{"comments": 4, "defects_found": 1, "hours_to_review": 3},
{"comments": 6, "defects_found": 2, "hours_to_review": 5},
{"comments": 2, "defects_found": 0, "hours_to_review": 2},
]
metrics = compute_review_metrics(sample)
for key, val in metrics.items():
print(f"{key}: {val}")
Expected output:
total_reviews: 3
avg_comments: 4.0
avg_defects_found: 1.0
avg_turnaround_hours: 3.3
Common Errors in Code Reviews
| # | Mistake | Explanation | Fix |
|---|---|---|---|
| 1 | Reviewing too late | Starting review when the PR is already large or urgent | Review often and early — break work into small PRs |
| 2 | Nitpicking style | Debating indentation, semicolons, or naming preferences | Use formatters (Code Quality Tools like Prettier, Black) to eliminate style arguments |
| 3 | Rubber-stamping | Approving without reading — "LGTM, ship it" | Block 30 minutes daily for focused review time |
| 4 | Reviewing for too long | Spending hours on a single PR, causing diminishing returns | Set a 60-minute max per review session; revisit fresh later |
| 5 | Personal attacks | Criticism directed at the person, not the code | Use "this approach has a race condition" not "you always forget thread safety" |
| 6 | No response to comments | Author merges without addressing reviewer feedback | Require acknowledge or resolution of every comment before merge |
| 7 | Asymmetric review load | Same people review everyone else's code while others never review | Rotate reviewing duties; track who reviews and who doesn't |
Learning Path
flowchart LR
A[Software Quality Overview] --> B[Code Reviews — Best Practices]
B --> C[Pair Programming]
B --> D[Code Quality Tools]
C --> E[Test-Driven Development]
D --> E
E --> F[Acceptance Testing]
style B fill:#4a90d9,stroke:#fff,color:#fff
style A fill:#e67e22,stroke:#fff,color:#fff
style D fill:#e67e22,stroke:#fff,color:#fff
Code reviews connect closely with Pair Programming (real-time review) and Code Quality Tools (automated checks that reduce the review burden). Start with the Software Quality Overview for the full picture.
Practice Questions
1. What is the optimal size for a code review?
200–400 lines of changed code. Larger reviews have diminishing defect-detection rates.2. How should a reviewer handle a style preference?
Preface it with "nit:" (nitpick) so the author knows it is optional. Better yet, enforce style with automated formatting tools.3. What is the recommended maximum turnaround time for a review?
One business day (24 hours). Reviews sitting longer than 3 days significantly slow down the team.4. What should a reviewer do if a PR contains both refactoring and new features?
Ask the author to split the PR. Mixing concerns makes it impossible to review either change effectively.5. How do you measure whether code reviews are effective?
Track turnaround time, defects found per review, review coverage, and comments per review. Compare trends over time.Challenge
Write a Python script that uses the GitHub API (or simulates it) to analyze the review history of a repository. Calculate the average time between PR creation and first review comment for each author. Return the top 3 slowest-reviewed authors.
Real-World Task
For your next pull request, add a "Reviewer Guide" section in the PR description that lists the three most important things you want reviewed. After the review, ask your reviewer if the guide helped them focus.
FAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Next lesson: Pair Programming — take review collaboration to the next level by pairing in real time.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro