Code Review Best Practices
Core Principles
Review Mindset:
-
Be constructive, explain the "why"
-
Prioritize by severity (critical vs nice-to-have)
-
Suggest alternatives, not just problems
-
Acknowledge good work
Goals:
-
Catch bugs before production
-
Improve code quality
-
Share knowledge
-
Prevent security vulnerabilities
-
Ensure consistency
Security Review
OWASP Top 10 Critical Checks
Injection Attacks:
-
SQL: Use parameterized queries, never concatenate user input
-
Command: Avoid shell commands with user input, sanitize properly
-
Code: Validate all eval(), exec(), dynamic execution
-
NoSQL/LDAP/XML: Use safe APIs
Authentication & Authorization:
-
Verify auth checks on protected endpoints
-
Proper session management (timeout, secure cookies)
-
Authorization logic prevents privilege escalation
-
Password policies enforced
Sensitive Data:
-
No hardcoded secrets (API keys, passwords, tokens)
-
Encryption at rest and in transit
-
PII/PHI minimally logged
-
No sensitive data in URLs or error messages
XSS Prevention:
-
All user input escaped/sanitized for output context
-
Content-Security-Policy headers configured
-
Framework built-in escaping (React JSX, template engines)
Deserialization:
-
Never deserialize untrusted data without validation
-
Prefer JSON over pickle/marshal
-
Validate object types post-deserialization
Misconfiguration:
-
No default credentials in production
-
Error messages don't leak internals
-
Security headers present (HSTS, X-Frame-Options)
API Security:
-
Rate limiting on sensitive endpoints
-
CORS properly configured
-
Input validation on all endpoints
-
No sensitive data in GET requests
Common Security Issues
-
Path Traversal: Validate paths, prevent ../ attacks
-
SSRF: Validate URLs, restrict internal network access
-
Open Redirects: Whitelist redirect destinations
-
Race Conditions: Check TOCTOU bugs
-
Timing Attacks: Constant-time comparison for secrets
-
Regex DoS: Avoid complex regex on user input
Code Quality
Readability & Maintainability
Naming:
-
Clear, descriptive names that reveal intent
-
Consistent conventions (camelCase, snake_case, PascalCase)
-
Avoid abbreviations unless domain-standard
Function Design:
-
Single responsibility, small (<50 lines ideal)
-
Clear input/output, minimal side effects
-
Pure functions where possible
Complexity:
-
Low cyclomatic complexity (<10)
-
Avoid deep nesting (max 3-4 levels)
-
Early returns to reduce nesting
-
Extract complex conditions into named functions
Documentation:
-
Comments explain "why", not "what"
-
Public APIs documented
-
No commented-out code (use version control)
Error Handling
-
All errors handled (catch, log, recover or fail fast)
-
No bare except/catch without handling
-
Informative error messages (what, why, action)
-
Proper error types (not generic Exception)
-
Resources cleaned up (finally/defer/using)
Anti-patterns:
-
Swallowing exceptions silently
-
Catching too broadly
-
Using exceptions for flow control
-
Returning null instead of error
-
Not validating inputs
Code Duplication
-
No copy-paste code blocks
-
Extract common logic to reusable functions
-
Use inheritance/composition appropriately
-
Be pragmatic: 3+ copies = refactor time
Performance
Algorithm & Data Structure Efficiency
-
Check time complexity (O(n²) → O(n log n) or O(n))
-
Appropriate space complexity
-
Right data structure (HashMap vs Array, Set vs List)
-
Efficient algorithms for common problems
Common Issues
Database:
-
N+1 query problems (use joins/batch)
-
Missing indexes on filtered/sorted columns
-
SELECT * instead of specific columns
-
Queries inside loops
Caching:
-
Repeated expensive calculations
-
Duplicate API calls
-
Static data not cached
-
Appropriate cache TTL
Resource Management:
-
Files/connections/streams closed
-
No memory leaks (circular refs, event listeners)
-
Unbounded collections (need limits/pagination)
Frontend:
-
Unnecessary re-renders (React useMemo, useCallback)
-
Large bundles (code splitting)
-
Images not optimized
-
Blocking JavaScript in critical path
Architecture & Design
SOLID Principles
-
Single Responsibility: One reason to change
-
Open/Closed: Open for extension, closed for modification
-
Liskov Substitution: Subtypes substitutable for base types
-
Interface Segregation: Small, focused interfaces
-
Dependency Inversion: Depend on abstractions
Other Key Principles
-
DRY: Don't Repeat Yourself
-
KISS: Keep It Simple
-
YAGNI: Don't over-engineer
-
Separation of Concerns: Distinct responsibilities
-
Composition over Inheritance
Code Structure
-
Proper layer separation (presentation, business, data)
-
Dependencies flow one direction
-
No circular dependencies
-
Modules cohesive and loosely coupled
-
Configuration separated from code
Testing
Coverage & Quality
-
Critical paths tested
-
Edge cases covered (empty, null, max values)
-
Error paths tested
-
Tests deterministic (no flaky tests)
-
Tests isolated (no shared state)
Test Quality:
-
Arrange-Act-Assert structure
-
One assertion focus per test
-
Descriptive test names
-
No test logic (tests are simple)
-
Realistic test data
-
External dependencies mocked appropriately
Review Process
Before Review
-
Understand context (PR description, tickets)
-
Check scope (< 400 lines ideal)
-
Run code locally
-
Verify CI passes
During Review
-
Start with architecture/approach
-
Use security/performance/quality checklists
-
Review tests first (explain intended behavior)
-
Ask questions, don't assume
Providing Feedback
Structure:
-
Severity: Critical (must fix) vs Nice-to-have
-
What: Specific issue
-
Why: Why it matters
-
How: Concrete alternative
Tone:
-
Use "we" language: "We should..." not "You should..."
-
Ask questions: "Have we considered...?"
-
Be specific: "Function has complexity 32, consider refactoring"
Example: ❌ "This is bad" ✅ "Using string concatenation in loop creates O(n²) complexity. Use StringBuilder for O(n)."
Priority Checklist
Critical (Must Fix)
-
Security vulnerabilities (injection, XSS, auth bypass)
-
Data loss/corruption risks
-
Memory/resource leaks
-
Breaking API changes without versioning
-
Race conditions/concurrency bugs
High Priority
-
Performance issues (O(n²), N+1 queries)
-
Missing input validation
-
Hardcoded configuration
-
Missing error logging
-
No tests for new functionality
Medium Priority
-
Code duplication (3+ instances)
-
High complexity (>15 cyclomatic)
-
Missing documentation for public APIs
-
Deep nesting (>4 levels)
-
Large functions (>100 lines)
Low Priority
-
Minor style inconsistencies
-
Could be more idiomatic
-
Variable names could be clearer
-
Magic numbers → constants
Language-Specific Notes
JavaScript/TypeScript:
-
Use const over let, avoid var
-
Proper async/await (handle rejections)
-
Use === not ==
-
TypeScript: Proper types, avoid any
-
Null/undefined handling
Python:
-
Follow PEP 8
-
Type hints for public APIs
-
Context managers (with)
-
No mutable default arguments
Java:
-
Proper exception hierarchy
-
Try-with-resources
-
Access modifiers (private/protected/public)
-
Immutability (final fields)
Go:
-
Error handling on every call
-
defer for cleanup
-
No goroutine leaks (context cancellation)
-
Small, focused interfaces
Best Practices Summary
-
Security First: OWASP Top 10 vulnerabilities
-
Test Coverage: Meaningful tests exist
-
Error Handling: All errors handled
-
Performance: Watch O(n²), N+1, memory leaks
-
Readability: Clear, self-documenting code
-
Maintainability: Low complexity, no duplication
-
Documentation: Public APIs documented
-
Consistency: Follow team conventions
-
Constructive: Be helpful, not critical
-
Prioritize: Critical first, style last