| name | engineering-code-review |
| description | Code review practices, PR etiquette, constructive feedback, automation tools, and effective review workflows |
Code Review Best Practices
Scope: Comprehensive guide to code review processes, PR etiquette, constructive feedback, automation, and team collaboration
Lines: ~350
Last Updated: 2025-10-27
Format Version: 1.0 (Atomic)
When to Use This Skill
Activate this skill when:
- Conducting code reviews for pull requests
- Establishing code review standards for a team
- Training engineers on giving and receiving feedback
- Setting up automated review tools (linters, static analysis)
- Defining PR templates and review checklists
- Resolving code review conflicts or disagreements
- Optimizing review turnaround time
- Balancing thoroughness with velocity
Core Concepts
Concept 1: The Purpose of Code Review
Primary Goals:
- Catch bugs: Find logical errors, edge cases, security issues before production
- Knowledge sharing: Spread understanding of codebase across team
- Maintain quality: Enforce standards, consistency, best practices
- Mentorship: Help junior engineers grow through feedback
- Collective ownership: Everyone responsible for code quality
Not the Goal:
- Nitpicking style preferences (use automated formatters)
- Asserting dominance or "being right"
- Rewriting code to match your personal style
- Blocking PRs indefinitely over minor issues
Concept 2: Reviewer Responsibilities
What to Review:
- Correctness: Does the code do what it claims?
- Testing: Are there adequate tests? Do they cover edge cases?
- Security: Any vulnerabilities (SQL injection, XSS, auth bypass)?
- Performance: Any obvious bottlenecks (N+1 queries, unnecessary loops)?
- Maintainability: Is the code readable and well-structured?
- Documentation: Are complex parts explained?
How to Review:
- Start with the PR description - does it explain the change?
- Review tests first - they document expected behavior
- Read code top-to-bottom, following the logical flow
- Ask questions rather than making demands
- Suggest alternatives, don't just say "this is wrong"
- Approve with minor suggestions rather than blocking
Concept 3: Author Responsibilities
Before Submitting:
- Self-review your own code first
- Run tests locally - all passing
- Update documentation if behavior changed
- Keep PRs small (< 400 lines ideal, < 800 max)
- Write clear PR description with context
During Review:
- Respond to feedback promptly
- Ask for clarification if feedback is unclear
- Explain your reasoning without being defensive
- Be open to suggestions - reviewers offer valuable perspective
- Push back respectfully when you disagree
Patterns
Pattern 1: Effective PR Description Template
Good Example:
## What Changed
Added user profile photo upload with S3 storage and CloudFront CDN.
## Why
Users have requested profile photos (50+ tickets). Unblocks social
features planned for Q1 2025.
## How
- New `POST /api/users/:id/photo` endpoint
- Uploads to S3 bucket `user-photos-prod`
- CloudFront distribution for fast global delivery
- Max 5MB file size, JPEG/PNG only
- Image resized to 400x400px on upload
## Testing
- Unit tests: upload validation, file type checking
- Integration tests: end-to-end upload flow
- Manual testing: tested on Chrome, Safari, Mobile Safari
## Screenshots
[Attach screenshot of new feature]
## Related
- Closes #1234
- Follow-up: Add photo cropping (tracked in #1235)
Bad Example:
## What Changed
Added profile photos.
## Testing
Tested manually, works fine.
Why Bad: No context on why, no details on implementation, vague testing description.
Pattern 2: Constructive Feedback
Good Examples:
# Asking Questions
❓ What happens if the user uploads a 20MB file? Should we validate
size on the client side too?
# Suggesting Improvements
💡 Consider extracting this validation logic into a separate function
for reusability:
```python
def validate_photo_upload(file):
if file.size > 5 * 1024 * 1024:
raise ValidationError("File too large")
if file.content_type not in ["image/jpeg", "image/png"]:
raise ValidationError("Invalid file type")
Pointing Out Issues
⚠️ This could cause a race condition if two uploads happen
simultaneously. Consider using a unique filename:
filename = f"{user_id}_{uuid4()}.jpg"
Praising Good Work
✅ Nice use of the factory pattern here! Makes testing much easier.
**Bad Examples**:
```markdown
# Too Vague
This doesn't look right.
# Overly Critical
This is terrible. Did you even test this?
# Nitpicking Without Tools
Please add spaces around operators. (Use automated formatter instead!)
# Making Demands
Change this to use a factory pattern.
# Better Alternative
Consider using a factory pattern here - it would make testing easier
and improve separation of concerns. What do you think?
Pattern 3: Review Checklist
Pre-Merge Checklist:
## Functionality
- [ ] Code does what PR description claims
- [ ] Edge cases handled (null, empty, large inputs)
- [ ] Error cases handled gracefully
## Testing
- [ ] Unit tests added for new logic
- [ ] Integration tests for new endpoints
- [ ] Tests actually test the behavior (not just mocks)
- [ ] All tests passing in CI
## Security
- [ ] No SQL injection vulnerabilities
- [ ] Input validation on all user data
- [ ] Authentication/authorization checked
- [ ] Sensitive data not logged
## Performance
- [ ] No N+1 query problems
- [ ] Database queries indexed
- [ ] No blocking operations in hot paths
- [ ] Large datasets paginated
## Maintainability
- [ ] Code is readable and well-organized
- [ ] Complex logic documented
- [ ] No TODO/FIXME comments (create tickets instead)
- [ ] Naming is clear and consistent
## Documentation
- [ ] API documentation updated
- [ ] README updated if needed
- [ ] Migration guide if breaking change
Pattern 4: PR Size Guidelines
Ideal PR Sizes:
| Size | Lines Changed | Review Time | Quality |
|---|
| Tiny | 1-50 | 5-10 min | Excellent |
| Small | 51-200 | 15-30 min | Good |
| Medium | 201-400 | 30-60 min | Acceptable |
| Large | 401-800 | 1-2 hours | Risky |
| Huge | 800+ | 2+ hours | Avoid |
Breaking Down Large PRs:
PR
- Database models
- API endpoints
- Frontend components
- Tests
- Documentation
PR
PR
PR
PR
Benefits:
- Faster review turnaround
- More thorough review (not overwhelming)
- Easier to revert if issues found
- Better git history for debugging
Pattern 5: Automated Review Tools
Pre-Commit Hooks:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/psf/black
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
hooks:
- id: flake8
- repo: https://github.com/pre-commit/mirrors-eslint
hooks:
- id: eslint
GitHub Actions CI:
name: PR Checks
on: [pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run linters
run: |
npm run lint
npm run type-check
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tests
run: npm test
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Security scan
uses: snyk/actions/node@master
Code Coverage Requirements:
coverage:
status:
project:
default:
target: 80%
threshold: 2%
Pattern 6: Handling Disagreements
Escalation Path:
1. Discuss in PR comments
↓ (if no resolution)
2. Hop on quick call/screenshare
↓ (if still no resolution)
3. Tag tech lead or architect
↓ (if still no resolution)
4. Document both approaches, ship one, revisit later
Example Disagreement Resolution:
# Original Feedback
@reviewer: This should use dependency injection instead of direct
instantiation.
# Author Response
@author: I considered that, but this is a one-off utility function
that's only called in tests. Adding DI feels like over-engineering.
What's the specific benefit you see?
# Reviewer Clarification
@reviewer: Fair point. My concern is if we later need to mock this in
other tests, but you're right that's not needed today. Let's ship this
and refactor if that need arises. Approved!
Best Practices
For Reviewers
- Review within 24 hours - Don't block teammates
- Start with what's good - Praise before criticism
- Be specific - Link to docs, provide examples
- Ask, don't tell - "What do you think about X?" vs "Do X"
- Approve with suggestions - Don't block on minor issues
- Review in small batches - Better focus, faster feedback
For Authors
- Self-review first - Catch your own mistakes
- Keep PRs small - < 400 lines when possible
- Write context - Help reviewers understand the change
- Respond promptly - Don't let PRs go stale
- Be grateful - Reviewers are helping you improve
- Push back respectfully - You know the code best
For Teams
- Define standards - Document what to review
- Automate style - Don't waste human time on formatting
- Rotate reviewers - Spread knowledge, prevent bottlenecks
- Track metrics - PR size, review time, bug escape rate
- Celebrate good reviews - Recognize thorough, helpful feedback
Anti-Patterns
Common Mistakes
❌ Reviewing 2000-line PRs
→ Break into smaller PRs
❌ Nitpicking style without automation
→ Use formatters (black, prettier, gofmt)
❌ Blocking PRs over minor issues
→ Approve with suggestions for follow-up
❌ Reviewing only for bugs
→ Also review for maintainability, performance, security
❌ Being overly critical without praise
→ Balance criticism with appreciation
❌ Reviewing too slowly (> 48 hours)
→ Review within 24 hours or reassign
❌ Rubber-stamping without reading
→ Actually review the code
❌ Rewriting code to match your style
→ Respect author's approach if it's reasonable
Code Examples
Python: Good vs Bad PR Structure
Bad: Too Large:
Good: Focused PRs:
class User(models.Model):
email = models.EmailField(unique=True)
password_hash = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
@api.post("/auth/login")
def login(email: str, password: str) -> Token:
user = User.get_by_email(email)
if not user or not user.verify_password(password):
raise AuthenticationError()
return create_token(user.id)
Go: Reviewable Test Structure
Bad: Unclear Test:
func TestUser(t *testing.T) {
u := User{Email: "test@example.com"}
if u.Email != "test@example.com" {
t.Fail()
}
}
Good: Clear Test with Table-Driven Approach:
func TestUserEmailValidation(t *testing.T) {
tests := []struct {
name string
email string
wantErr bool
}{
{name: "valid email", email: "user@example.com", wantErr: false},
{name: "missing @", email: "userexample.com", wantErr: true},
{name: "empty", email: "", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
u := User{Email: tt.email}
err := u.Validate()
if (err != nil) != tt.wantErr {
t.Errorf("got error %v, wantErr %v", err, tt.wantErr)
}
})
}
}
TypeScript: Reviewable Component Structure
Bad: God Component:
export function UserDashboard() {
}
Good: Separated Concerns:
export function UserDashboard() {
const { user } = useAuth();
const { profile, loading } = useUserProfile(user.id);
if (loading) return <LoadingSpinner />;
return (
<div>
<ProfileHeader profile={profile} />
<ProfileForm profile={profile} onSave={handleSave} />
</div>
);
}
Level 3: Resources
For deep-dive learning and production-ready tools, see the resources/ directory:
Reference Materials
- REFERENCE.md (~900 lines): Comprehensive code review guide
- Complete review checklist (design, functionality, complexity, tests, naming, comments)
- Writing effective review comments (tone, structure, severity levels)
- Handling disagreements and conflict resolution
- Common code smells and fixes
- Review automation (linters, formatters, security scanners for Python, JavaScript, TypeScript, Rust, Go)
- GitHub PR workflows and branch protection
- Review metrics (turnaround time, review depth, defect escape rate)
Scripts (resources/scripts/)
-
review_pr.py: Automated PR review script
- Runs linters, checks tests, analyzes diff
- Detects common issues (debug statements, secrets, TODOs)
- Generates human or JSON reports
- CLI:
./review_pr.py --base main --json
-
analyze_review_metrics.py: GitHub PR metrics analyzer
- Tracks turnaround time, review depth, iteration count
- Reviewer leaderboard
- PR size distribution
- CLI:
./analyze_review_metrics.py --repo owner/repo --days 30 --json
-
generate_review_checklist.sh: Custom checklist generator
- Creates checklists by PR type (feature, bugfix, security, refactor, docs)
- Language-specific checks (Python, JavaScript, TypeScript, Rust, Go)
- Markdown or JSON output
- CLI:
./generate_review_checklist.sh --type security --lang python --output checklist.md
Examples (resources/examples/)
- github/PULL_REQUEST_TEMPLATE.md: Production-ready PR template
- github/code-review-workflow.yml: Complete GitHub Actions workflow
- Linting, security scanning, test execution
- PR size analysis with automated comments
- Reviewer assignment based on CODEOWNERS
- python/automated_review_checks.py: Python code reviewer
- Checks imports, function length, complexity, documentation, naming, anti-patterns, security
- checklists/security-review.md: Comprehensive security checklist
- Authentication, authorization, input validation, data protection, common vulnerabilities
- checklists/performance-review.md: Performance optimization checklist
- Database queries, algorithms, memory, network I/O, frontend performance
All scripts are executable, documented, and production-ready.
Related Skills
- engineering-code-quality: SOLID principles, code smells, metrics
- engineering-refactoring-patterns: When and how to refactor
- engineering-test-driven-development: Writing testable code
- engineering-technical-debt: Managing and prioritizing tech debt
- engineering-continuous-integration: Automating review checks
References