This document expands on the core code review principles outlined in copilot-instructions.md, providing detailed guidelines, examples, and strategies for conducting effective code reviews.
Understanding Code Review Principles
Principle 1: Review Intent, Not Just Implementation
What This Means: Code review should start with understanding why the change is being made before evaluating how it’s implemented. This prevents premature criticism and helps reviewers provide more relevant feedback.
Key Questions to Ask:
- What problem is this change trying to solve?
- Is this the right problem to solve at this time?
- Does the solution align with the project’s architecture and goals?
- Are there simpler or more maintainable alternatives?
Example Scenarios:
Bad Review Approach:
"This function is too complex. You should split it into smaller functions."
Good Review Approach:
"I see this function handles user authentication. Looking at the requirements in issue #123,
could we simplify this by using the existing AuthService instead of implementing
custom logic? This would reduce complexity and maintain consistency with the rest of the codebase."
When to Apply:
- Before making any inline comments, read the entire PR description and linked issues
- Review the design decision before critiquing implementation details
- Consider the trade-offs the author may have faced
Research Backing:
- Microsoft Research found that understanding intent reduces back-and-forth by 40%
- Google’s code review guidelines emphasize “understanding the CL” as the first step
Principle 2: Focus on Impact and Correctness
What This Means: Prioritize issues that affect functionality, security, and performance over stylistic preferences. Not all feedback carries equal weight.
Priority Hierarchy:
- Critical: Correctness, security vulnerabilities, data loss risks
- High: Performance issues, scalability problems, architectural inconsistencies
- Medium: Maintainability concerns, missing tests, inadequate error handling
- Low: Style preferences, minor optimizations, documentation improvements
Example Scenarios:
Critical Issue (Must Fix):
# Bad: SQL Injection vulnerability
query = f"SELECT * FROM users WHERE id = {user_input}"
# Good: Parameterized query
query = "SELECT * FROM users WHERE id = ?"
cursor.execute(query, (user_input,))
High Priority Issue (Should Fix):
// Bad: O(n²) algorithm processing large datasets
for (let i = 0; i < items.length; i++) {
for (let j = 0; j < items.length; j++) {
if (items[i].id === items[j].relatedId) {
// process
}
}
}
// Good: O(n) with hash map
const itemMap = new Map(items.map(item => [item.id, item]));
items.forEach(item => {
const related = itemMap.get(item.relatedId);
if (related) {
// process
}
});
Low Priority Issue (Nice to Have):
# Style preference - both are acceptable
result = calculate_total(items) # Current
result = calculateTotal(items) # Suggested (but project uses snake_case)
When to Apply:
- Use severity labels (Critical, High, Medium, Low) in your comments
- Distinguish between “must fix before merge” and “consider for future”
- Don’t block merges on low-priority issues if the PR is otherwise sound
Research Backing:
- SmartBear study shows focusing on high-impact issues increases defect detection by 60%
- Code reviews that prioritize correctness catch 75% of bugs before production
Principle 3: Be Objective and Specific
What This Means: Base feedback on facts, standards, and measurable criteria rather than personal preferences. Provide concrete examples and actionable suggestions.
Characteristics of Objective Feedback:
- References specific lines of code
- Cites documentation, standards, or team conventions
- Explains the reasoning with technical merit
- Offers concrete alternatives or solutions
- Avoids subjective language like “I don’t like” or “This feels wrong”
Example Scenarios:
Subjective Feedback (Avoid):
"This code is messy and hard to understand."
Objective Feedback (Prefer):
"The function at line 45 has a cyclomatic complexity of 12 (measured by
complexity tool), which exceeds our team standard of 10. Consider extracting
the validation logic (lines 50-65) into a separate validateInput() function.
This would improve testability and readability. See style guide section 3.2."
When Uncertain, Frame as Questions:
"I notice this implementation differs from the pattern used in AuthController
(line 23). Is there a specific reason for the different approach, or should
we maintain consistency? If there's a good reason for the difference, could
you add a comment explaining it?"
Providing Evidence:
- Link to relevant documentation: “According to OWASP guidelines…”
- Reference similar code: “We handle similar cases in UserService.ts:45”
- Cite metrics: “This increases bundle size by 15% (measured with webpack-bundle-analyzer)”
- Quote standards: “PEP 8 section 2.3 recommends…”
Research Backing:
- Studies show objective feedback is 3x more likely to be accepted
- Specific, actionable comments reduce revision cycles by 50%
Advanced Review Techniques
Context-Aware Review
Understand the Scope:
- Small fix (< 50 LOC): Focus on correctness and edge cases
- Feature addition (50-400 LOC): Review architecture, tests, and documentation
- Refactoring (> 400 LOC): Consider splitting; focus on preserving behavior
Consider the Author’s Experience:
- Junior developer: Provide educational feedback with explanations
- Senior developer: Focus on high-level design and architectural concerns
- New team member: Help them understand team conventions and patterns
The “Why” Behind Common Issues
Magic Numbers:
# Why it matters: Unclear intent, difficult to maintain
if user.age > 18: # What does 18 represent?
# Better: Self-documenting
MINIMUM_ADULT_AGE = 18
if user.age > MINIMUM_ADULT_AGE:
Deep Nesting:
// Why it matters: Difficult to reason about, high cognitive load
if (user) {
if (user.isActive) {
if (user.hasPermission('admin')) {
if (user.emailVerified) {
// Do something
}
}
}
}
// Better: Early returns reduce nesting
if (!user || !user.isActive) return;
if (!user.hasPermission('admin')) return;
if (!user.emailVerified) return;
// Do something
Missing Error Handling:
# Why it matters: Silent failures, difficult debugging
data = json.loads(response.text)
# Better: Explicit error handling
try:
data = json.loads(response.text)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse JSON: {e}")
return None
Balanced Review Approach
The 80/20 Rule:
- Spend 80% of review time on critical and high-priority issues
- Spend 20% on style and minor improvements
- If you find yourself making mostly style comments, reconsider your focus
Positive Reinforcement: While AI agents should remain objective, recognizing good practices helps establish patterns:
- “This error handling follows our established pattern from ApiService”
- “Good use of dependency injection here - makes this testable”
- “The added tests cover the edge cases mentioned in issue #456”
Know When to Stop:
- If you find >10 critical issues, consider rejecting and requesting rework
- If you find mostly style issues, consider approving with non-blocking suggestions
- If uncertain about design decisions, request a synchronous discussion
Review Checklist Expansion
Security Deep Dive
Input Validation:
- All user inputs are validated against a whitelist, not just sanitized
- Validation happens on the server-side, not just client-side
- File uploads check both extension and MIME type
- Size limits are enforced on all inputs
- Special characters are properly escaped for the context (SQL, HTML, etc.)
Authentication & Authorization:
- Authentication happens before any sensitive operations
- Authorization checks occur at the service layer, not just UI
- Session tokens have appropriate expiration times
- Password policies meet current NIST guidelines
- Multi-factor authentication is implemented where required
Data Protection:
- Sensitive data is encrypted at rest
- Sensitive data is encrypted in transit (HTTPS/TLS)
- Secrets are not hardcoded in source code
- Database credentials use environment variables or secure vaults
- PII is handled according to GDPR/CCPA requirements
Performance Deep Dive
Algorithmic Efficiency:
- No nested loops over large datasets (O(n²) or worse)
- Appropriate data structures used (Set for lookups, Map for key-value)
- Sorting algorithms are efficient for the data size
- Recursive functions have proper termination conditions
- Memoization is used for expensive repeated calculations
Database Operations:
- Queries use indexes effectively
- No N+1 query problems
- Batch operations used instead of individual updates
- Connection pooling is properly configured
- Transactions are used appropriately (not too large, not too small)
Resource Management:
- Files, streams, and connections are properly closed
- Memory leaks are prevented (no circular references, event listeners cleaned up)
- Large datasets are streamed or paginated, not loaded entirely into memory
- Caching is used where appropriate (with proper invalidation strategy)
- Background jobs don’t block the main thread
Code Quality Deep Dive
Readability:
- Variable names clearly indicate purpose and type
- Function names are verbs that describe actions
- Class names are nouns that describe entities
- Complex logic has explanatory comments
- Magic numbers are replaced with named constants
Modularity:
- Functions do one thing and do it well (Single Responsibility)
- Functions are small enough to fit on one screen (< 50 lines)
- Cyclomatic complexity is reasonable (< 10)
- Duplicated code is extracted into reusable functions
- Tight coupling is avoided through dependency injection
Error Handling:
- Exceptions are caught at appropriate levels
- Error messages are informative for debugging
- User-facing errors don’t expose sensitive information
- Failed operations are logged with context
- Retry logic is implemented for transient failures
Communication Strategies
Structuring Review Comments
Use the “Situation-Behavior-Impact” Framework:
Situation: "In the login endpoint (line 45)"
Behavior: "the password is logged in plain text"
Impact: "which exposes user credentials in log files and violates our security policy"
Solution: "Remove the password from the log statement or hash it before logging"
Handling Disagreements
When You Disagree with the Approach:
- First, ensure you understand the intent (ask questions)
- Acknowledge the valid aspects of the current approach
- Present your alternative with specific reasoning
- Suggest a synchronous discussion if it’s complex
Example:
"I see you're using a singleton pattern here. While this ensures a single
instance, it makes testing difficult because the state persists between tests.
Have you considered using dependency injection instead? This would allow us to
mock the service in tests. If the singleton pattern is required for a specific
reason I'm not aware of, could you add a comment explaining it? Happy to
discuss this synchronously if helpful."
Review Anti-Patterns to Avoid
The Nitpicker:
- Focuses exclusively on style and formatting
- Blocks PRs over trivial issues
- Fix: Use automated formatters; focus on substance
The Perfectionist:
- Demands ideal solutions when good-enough works
- Requests extensive refactoring beyond PR scope
- Fix: Distinguish “must fix” from “nice to have”
The Ghost:
- Takes days to review, blocking progress
- Provides minimal feedback
- Fix: Set review time expectations; prioritize reviews
The Architect:
- Questions every design decision
- Wants to redesign the entire system
- Fix: Focus on the changes in the PR; suggest architectural discussions separately
Metrics and Continuous Improvement
Measuring Review Effectiveness
Leading Indicators (Predict future quality):
- Review turnaround time: < 24 hours optimal
- Comments per 100 LOC: 3-7 is typical
- Percentage of PRs with at least one comment: 60-80%
- Review participation rate: 80%+ of team members reviewing
Lagging Indicators (Measure outcomes):
- Defect escape rate: Bugs found in production that should have been caught
- Post-release hotfix rate: Emergency fixes needed after deployment
- Technical debt accumulation: Time spent fixing old issues vs. building new features
- Customer-reported bugs: Issues that reach end users
Personal Improvement
For AI Agents:
- Track acceptance rate of different types of suggestions
- Identify patterns in dismissed comments (learn when to stay silent)
- Measure false positive rate and work to reduce it
- Learn project-specific conventions from accepted feedback
For Human Reviewers:
- Seek feedback on your reviews from authors
- Rotate reviewers to gain different perspectives
- Participate in review calibration sessions with the team
- Study code review patterns in high-quality open source projects
Language-Specific Considerations
While the core principles are universal, each language has specific patterns and pitfalls. Language-specific guidance will be provided in separate files:
code-review-instructions-python.md: Python-specific patterns (PEP 8, type hints, generators, etc.)code-review-instructions-javascript.md: JavaScript/TypeScript patterns (async/await, TypeScript types, etc.)code-review-instructions-java.md: Java patterns (null safety, streams, exception handling, etc.)code-review-instructions-go.md(planned): Go patterns (error handling, goroutines, interfaces, etc.)
Additional Resources
Books
- “Code Complete” by Steve McConnell - Comprehensive guide to code construction
- “The Art of Readable Code” by Boswell & Foucher - Focuses on code clarity
- “Best Kept Secrets of Peer Code Review” by SmartBear - Research-backed review strategies
Online Resources
- Google Engineering Practices
- Microsoft Code Review Guidelines
- Conventional Comments
- Code Review Developer Guide
Tools
- ESLint, Pylint, RuboCop: Automated style checking
- SonarQube: Code quality and security analysis
- CodeClimate: Automated code review
- Conventional Comments extension: Structured comment formatting
Conclusion
Effective code review is a skill that improves with practice and reflection. By focusing on intent, impact, and objectivity, reviewers provide value while maintaining a collaborative environment. Remember that the goal is not just to find bugs, but to:
- Improve code quality and maintainability
- Share knowledge across the team
- Establish and reinforce standards
- Mentor less experienced developers
- Prevent defects from reaching production
Use these guidelines as a foundation, adapt them to your team’s context, and continuously refine your approach based on measured outcomes.