Code Review Checklist
Practical checklist based on Martin Fowler principles for reviewing code quality, design, and testability
Design Quality (Beck's Four Rules)
1. Does it pass tests?
- All tests pass
- New tests added for new functionality
- Tests cover edge cases and error conditions
- Tests are meaningful, not just achieving coverage
2. Does it reveal intention?
- Clear, descriptive names for classes, functions, and variables
- Code is self-documenting through good naming
- Comments only where logic isn't self-evident
- Functions/methods do what their name suggests
3. Is there duplication?
- No copy-pasted code blocks
- Logic expressed "Once and Only Once"
- Similar code consolidated through abstraction
- BUT: Not abstracted prematurely (wait for third occurrence)
4. Are there unnecessary elements?
- No dead code or commented-out code
- No speculative features for hypothetical futures
- No unused variables, parameters, or imports
- No over-engineering or premature optimization
Responsibilities and SOLID
Single Responsibility
- Each class has one clear responsibility
- Each function does one thing well
- No "god objects" that know/do everything
- Changes to one concern don't affect unrelated code
Open/Closed
- Uses polymorphism for type-based variations
- New features added through extension, not modification
- Appropriate abstractions for varying behavior
Liskov Substitution
- Subtypes can replace base types without breaking behavior
- Polymorphic behavior is predictable
- No surprising exceptions or behavior changes in subclasses
Interface Segregation
- Interfaces are focused and cohesive
- No "fat interfaces" forcing unused method implementations
- Clients don't depend on interfaces they don't use
Dependency Inversion
- Depends on abstractions, not concrete implementations
- High-level modules independent of low-level details
- Dependencies injected, not created internally
Object-Oriented Design
Tell Don't Ask
- Objects are told what to do, not queried for decisions
- Behavior located with the data it operates on
- No excessive getter/setter chains
- Data and operations on that data are bundled together
Encapsulation
- Internal state properly hidden
- Appropriate access modifiers used
- No direct field access from outside
- Behavior exposes intent, not implementation details
Testability
Test Coverage
- Critical paths have test coverage
- New code includes tests
- Coverage is thoughtful, not just hitting percentages
- Tests validate behavior, not just exercise code
Test Quality
- Tests follow Arrange-Act-Assert pattern
- One focused assertion per test
- Clear test names describing what's being tested
- Tests are fast enough to run frequently
- No test duplication at multiple levels
Testable Design
- Dependencies can be injected for testing
- Appropriate seams for test doubles
- No hard-coded dependencies or globals
- Side effects separated from pure logic
Test Doubles Usage
- Appropriate test double type used (dummy, fake, stub, spy, mock)
- Not over-mocked (avoiding brittle tests)
- Mocks verify interactions when needed
- Stubs provide test data appropriately
Code Smells
Methods and Functions
- No long methods (check if it needs decomposition)
- No long parameter lists (consider parameter objects)
- No feature envy (method more interested in other class's data)
- Functions at single level of abstraction
Classes and Modules
- No large classes with too many responsibilities
- No data classes (just fields, no behavior)
- No god classes that know/do everything
- Classes are cohesive
Changes and Dependencies
- No divergent change (one class changing for multiple reasons)
- No shotgun surgery (one change affecting many classes)
- No inappropriate intimacy (classes too coupled)
- No circular dependencies
Data and Types
- No primitive obsession (use value objects where appropriate)
- No magic numbers (use named constants)
- Appropriate data structures for the task
Refactoring Quality
Code Structure
- Broken into small, focused functions
- Concerns separated (calculation from formatting, etc.)
- Polymorphism used for type-based variations
- Appropriate design patterns applied
Refactoring Safety
- Tests existed before refactoring
- Small, incremental changes
- Tests still pass after changes
- Commits show safe progression
Architecture and Organization
Module Organization
- Related code grouped together
- Clear module boundaries
- Dependencies flow in one direction (no cycles)
- Stable dependencies principle followed
Bounded Contexts
- Clear context boundaries in large systems
- Each context has cohesive internal model
- Explicit integration points between contexts
- No forced unification of incompatible models
Layering
- Appropriate separation of concerns across layers
- Business logic separate from presentation
- Infrastructure separate from domain
- Dependencies point inward (dependency inversion)
Dependency Management
Dependency Injection
- Dependencies injected via constructor or setter
- Required dependencies via constructor
- Optional dependencies via setter
- No service locator pattern in components
Inversion of Control
- Components don't create their dependencies
- Configuration separated from usage
- Framework calls component, not vice versa
General Code Quality
Readability
- Code is easy to understand
- Consistent formatting and style
- Appropriate level of abstraction
- No clever tricks that obscure meaning
Maintainability
- Easy to modify and extend
- Changes are localized
- No tight coupling
- Well-organized and structured
Performance
- No premature optimization
- Efficient algorithms where it matters
- No obvious performance issues
- Performance trade-offs justified
Anti-Patterns to Reject
- No test coverage targets driving low-quality tests
- No over-engineering for hypothetical futures
- No asking objects for data then making external decisions
- No creating dependencies inside components
- No inverted test pyramid (only E2E tests)
- No slow tests discouraging execution
- No test duplication without additional value
- No ignoring code smells without investigation
- No refactoring without tests
- No components dependent on framework locators
Review Process
Before Approving
- Code meets acceptance criteria
- All checklist items addressed or have justification
- Tests are comprehensive and passing
- No unaddressed code smells
- Architecture aligns with system design
- Documentation updated if needed
Providing Feedback
- Reference specific code locations
- Explain why something is problematic
- Suggest concrete improvements
- Link to relevant principles/patterns
- Balance critical feedback with positive observations
- Focus on the code, not the person
Context and Balance
Remember:
- These are guidelines, not absolute rules
- Apply principles pragmatically based on context
- Balance multiple concerns in design decisions
- Simple solutions are often the best solutions
- Perfect is the enemy of good