Code Review Best Practices for AI-Generated Code
AI-generated code requires careful review. While Copilot can dramatically speed up development, accepting suggestions blindly can introduce bugs, security vulnerabilities, and maintenance headaches. This lesson covers what to look for and how to review effectively.
The Mindset for Reviewing AI Code
Think of Copilot as a junior developer who:
- Types very fast
- Knows many patterns and languages
- Sometimes makes subtle mistakes
- Doesn't understand your specific business requirements
- May use outdated or insecure patterns
Your job is to be the senior developer who reviews their work.
Security Review Checklist
SQL Injection
Copilot sometimes generates vulnerable SQL:
// DANGEROUS - Copilot might suggest this:
const query = `SELECT * FROM users WHERE email = '${email}'`;
// SAFE - Always use parameterized queries:
const query = 'SELECT * FROM users WHERE email = $1';
const result = await db.query(query, [email]);
Always check: Are user inputs ever concatenated into queries?
Cross-Site Scripting (XSS)
// DANGEROUS - Direct HTML insertion:
element.innerHTML = userProvidedContent;
// SAFE - Use text content or sanitization:
element.textContent = userProvidedContent;
// Or sanitize: element.innerHTML = DOMPurify.sanitize(userProvidedContent);
Always check: Is user input ever rendered as HTML without sanitization?
Command Injection
# DANGEROUS - Shell command with user input:
import os
os.system(f"convert {user_filename} output.png")
# SAFE - Use subprocess with argument list:
import subprocess
subprocess.run(['convert', user_filename, 'output.png'], check=True)
Always check: Are user inputs passed to shell commands?
Hardcoded Secrets
// DANGEROUS - Copilot might auto-complete from training data:
const API_KEY = 'sk-abc123...';
// SAFE - Use environment variables:
const API_KEY = process.env.API_KEY;
Always check: Are there any hardcoded credentials, API keys, or secrets?
Path Traversal
# DANGEROUS - User controls file path:
def read_file(filename):
return open(f'/uploads/{filename}').read()
# Attack: filename = '../../../etc/passwd'
# SAFE - Validate and sanitize:
import os
def read_file(filename):
safe_name = os.path.basename(filename) # Remove path components
return open(f'/uploads/{safe_name}').read()
Logic Review Checklist
Off-by-One Errors
A classic Copilot mistake:
// Bug: Misses the last element
for (let i = 0; i < array.length - 1; i++) {
// Should be: i < array.length
}
// Bug: Index out of bounds
const lastItem = array[array.length];
// Should be: array[array.length - 1]
Null/Undefined Handling
// Bug: Doesn't handle null case
function getUserName(user) {
return user.name.toUpperCase(); // Crashes if user is null
}
// Fixed: Null check
function getUserName(user) {
return user?.name?.toUpperCase() ?? 'Anonymous';
}
Edge Cases
# Copilot might miss edge cases:
def average(numbers):
return sum(numbers) / len(numbers) # Crashes on empty list!
# Fixed:
def average(numbers):
if not numbers:
return 0
return sum(numbers) / len(numbers)
Incorrect Assumptions
// Bug: Assumes data structure
function getFirstUser(response) {
return response.data.users[0].name;
}
// What if response.data is null? What if users is empty?
// Fixed: Defensive coding
function getFirstUser(response) {
const users = response?.data?.users;
return users?.[0]?.name ?? null;
}
Performance Review
Unnecessary Operations
// Inefficient: Creates new array every iteration
items.forEach(item => {
const filtered = items.filter(i => i.category === item.category);
// Do something with filtered
});
// Better: Group once
const grouped = items.reduce((acc, item) => {
acc[item.category] = acc[item.category] || [];
acc[item.category].push(item);
return acc;
}, {});
N+1 Query Problems
// Inefficient: N+1 database queries
const users = await getUsers();
for (const user of users) {
user.posts = await getPostsByUser(user.id); // Query per user!
}
// Better: Batch query
const users = await getUsers();
const userIds = users.map(u => u.id);
const allPosts = await getPostsByUserIds(userIds); // Single query
Memory Leaks
// Bug: Event listener never removed
useEffect(() => {
window.addEventListener('resize', handleResize);
// Missing cleanup!
}, []);
// Fixed: Cleanup function
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
Code Quality Review
Readability
// Hard to read - Copilot sometimes over-compresses:
const r = a.filter(x => x.s === 'a').map(x => x.v).reduce((a,b) => a+b, 0);
// Better: Clear variable names and steps:
const activeItems = items.filter(item => item.status === 'active');
const values = activeItems.map(item => item.value);
const total = values.reduce((sum, val) => sum + val, 0);
Maintainability
# Magic numbers - what does 86400 mean?
if elapsed > 86400:
expire_session()
# Better: Named constant
SECONDS_PER_DAY = 86400
if elapsed > SECONDS_PER_DAY:
expire_session()
Consistency
Check that Copilot's suggestions match your codebase:
- Naming conventions (camelCase vs snake_case)
- Error handling patterns
- Import ordering
- Code structure
The Review Process
1. Understand Before Accepting
Never accept code you don't understand:
# If Copilot suggests this and you don't know what it does:
result = re.sub(r'(?<!\\)(?:\\\\)*\\n', '\n', text)
# Don't accept it! Ask Copilot Chat to explain it first.
2. Test the Code
Before committing AI-generated code:
- Run existing tests
- Write new tests for the generated code
- Test edge cases manually
- Check error handling paths
3. Check Dependencies
Copilot might suggest using libraries:
- Is the library already in your project?
- Is it well-maintained and secure?
- Does it match your project's license requirements?
// Copilot suggests: import moment from 'moment';
// But moment is deprecated - use date-fns or dayjs instead
4. Verify API Usage
Copilot may hallucinate APIs that don't exist:
// Copilot suggests a method that doesn't exist:
const result = await fetch(url).json(); // Wrong! .json() needs await
// Correct:
const response = await fetch(url);
const result = await response.json();
Review Tools and Techniques
Use Static Analysis
Configure ESLint, TypeScript, or other linters to catch issues:
// ESLint catches potential issues:
// 'result' is assigned but never used
const result = calculateValue();
return; // Missing return of result
Use Type Checking
TypeScript catches many Copilot mistakes:
// TypeScript catches type mismatches:
function add(a: number, b: number): number {
return a + b;
}
add("1", "2"); // Error: Argument of type 'string' is not assignable to 'number'
Run Tests Frequently
After accepting Copilot suggestions, run your test suite:
npm test # Catch regressions early
Red Flags in AI-Generated Code
Watch out for these warning signs:
| Red Flag | Why It's Concerning |
|---|---|
| Overly complex one-liners | Hard to debug and maintain |
| Unfamiliar libraries | May be outdated or insecure |
| TODO comments | Incomplete implementation |
| Console.log statements | Debug code left in |
| Commented-out code | Unclear intent |
| Generic variable names | Hard to understand |
| Missing error handling | Will crash in production |
| Synchronous operations that should be async | Performance issues |
Summary
Reviewing AI-generated code requires vigilance:
Security:
- Check for SQL injection, XSS, command injection
- Look for hardcoded secrets
- Validate user input handling
Logic:
- Watch for off-by-one errors
- Check null/undefined handling
- Test edge cases
- Question assumptions about data
Performance:
- Look for unnecessary operations
- Check for N+1 query patterns
- Watch for memory leaks
Quality:
- Ensure readability
- Check for consistency with your codebase
- Verify maintainability
Process:
- Understand before accepting
- Test thoroughly
- Use static analysis tools
- Run your test suite
With these practices, you can safely leverage Copilot's speed while maintaining code quality and security.
In the final lesson, you'll learn about Copilot's limitations and how it compares to other AI coding tools.

