Code review practices, PR etiquette, constructive feedback, automation tools, and effective review workflows
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)
Activate this skill when:
Primary Goals:
Not the Goal:
What to Review:
How to Review:
Before Submitting:
During Review:
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.
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:
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:
# 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?
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
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:
# Bad: One massive PR
PR #1: "Implement entire authentication system" (2000 lines)
- Database models
- API endpoints
- Frontend components
- Tests
- Documentation
# Good: Multiple focused PRs
PR #1: "Add User and Session database models" (150 lines)
PR #2: "Add authentication API endpoints" (200 lines)
PR #3: "Add login/signup UI components" (180 lines)
PR #4: "Add authentication integration tests" (120 lines)
Benefits:
Pre-Commit Hooks:
# .pre-commit-config.yaml
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 # Python formatter
- repo: https://github.com/pycqa/flake8
hooks:
- id: flake8 # Python linter
- repo: https://github.com/pre-commit/mirrors-eslint
hooks:
- id: eslint # JavaScript linter
GitHub Actions CI:
# .github/workflows/pr-checks.yml
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:
# codecov.yml
coverage:
status:
project:
default:
target: 80% # Fail PR if coverage drops below 80%
threshold: 2% # Allow 2% decrease
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!
โ 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
Bad: Too Large:
# PR: Implement entire user management system (1500 lines)
# - User model, authentication, authorization, profile, settings
# - Impossible to review thoroughly
Good: Focused PRs:
# PR 1: Add User model and migrations (100 lines)
class User(models.Model):
email = models.EmailField(unique=True)
password_hash = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
# PR 2: Add authentication endpoints (150 lines)
@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)
# PR 3: Add profile endpoints (120 lines)
# PR 4: Add integration tests (100 lines)
Bad: Unclear Test:
func TestUser(t *testing.T) {
// What is this testing?
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)
}
})
}
}
Bad: God Component:
// Bad: 800-line component doing everything
export function UserDashboard() {
// Authentication logic
// Data fetching
// Form handling
// Validation
// Rendering
// Hard to review!
}
Good: Separated Concerns:
// Good: Small, focused components
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>
);
}
// Each component is small, testable, reviewable
For deep-dive learning and production-ready tools, see the resources/ directory:
resources/scripts/)./review_pr.py --base main --json./analyze_review_metrics.py --repo owner/repo --days 30 --json./generate_review_checklist.sh --type security --lang python --output checklist.mdresources/examples/)All scripts are executable, documented, and production-ready.