Review & Refactor AI Code: Patterns for Quality

# Review, Refactor, and Improve Patterns You've just watched your AI assistant generate hundreds of lines of code in seconds. It works. Tests pass. You're tempted to merge and move on. But here's the truth: AI-generated code is a first draft, not a finished product. The real skill in vibe coding isn't just getting AI to write code—it's knowing how to review, refactor, and systematically improve what it produces. This lesson teaches you patterns for turning AI-generated code into production-quality software. You'll learn what to look for, when to refactor, and how to build a reliable review process that catches issues before they become technical debt. ## Why AI Code Needs Different Review Patterns AI assistants don't think like human developers. They optimize for pattern matching and completion, not for your specific codebase's conventions, performance requirements, or long-term maintainability. This creates unique review challenges: - **Over-generic solutions**: AI often produces code that works broadly but isn't optimized for your specific use case - **Inconsistent patterns**: Different prompts can yield different coding styles, even within the same file - **Hidden complexity**: AI might implement features in convoluted ways that are harder to maintain - **Missing context**: AI doesn't know your team's conventions, performance requirements, or architectural decisions As covered in [hallucination-detection](/lessons/hallucination-detection), AI can also confidently suggest non-existent APIs or outdated approaches. Your review process needs to catch these issues systematically. ## The Three-Pass Review Pattern Instead of trying to catch everything in one review, use a structured three-pass approach. Each pass has a specific focus, making reviews faster and more thorough. ### Pass 1: Correctness and Safety Your first pass focuses on whether the code is correct and safe to run. This is where you catch the critical issues. **What to check:** ```python # AI-generated authentication function def authenticate_user(username, password): user = db.query(f"SELECT * FROM users WHERE username = '{username}'") if user and user.password == password: return user return None ``` **Critical issues:** - SQL injection vulnerability (string interpolation) - Plain text password comparison - No rate limiting or account lockout **Refactored version:** ```python from werkzeug.security import check_password_hash import time def authenticate_user(username: str, password: str) -> Optional[User]: # Use parameterized queries to prevent SQL injection user = db.query( "SELECT * FROM users WHERE username = ?", (username,) ).first() if not user: # Prevent timing attacks time.sleep(0.1) return None # Use proper password hashing if check_password_hash(user.password_hash, password): return user time.sleep(0.1) return None ``` **First-pass checklist:** - [ ] Security vulnerabilities ([security-considerations](/lessons/security-considerations)) - [ ] Error handling for edge cases - [ ] Type safety and null checks - [ ] Resource cleanup (files, connections, memory) - [ ] Input validation ### Pass 2: Design and Architecture Once you know the code is safe, evaluate whether it fits your application's architecture and follows good design principles. ```javascript // AI-generated user service - works but has design issues class UserService { async createUser(userData) { // Validation if (!userData.email || !userData.name) { throw new Error('Missing required fields'); } // Hash password const bcrypt = require('bcrypt'); const hashedPassword = await bcrypt.hash(userData.password, 10); // Save to database const db = require('./database'); const user = await db.users.create({ ...userData, password: hashedPassword }); // Send welcome email const nodemailer = require('nodemailer'); const transporter = nodemailer.createTransport(/* config */); await transporter.sendMail({ to: user.email, subject: 'Welcome!', text: `Welcome ${user.name}!` }); // Log the event console.log(`User created: ${user.id}`); return user; } } ``` **Design problems:** - Violates Single Responsibility Principle - Tight coupling to specific libraries - Hard to test - No dependency injection - Mixing concerns (validation, persistence, email, logging) **Refactored with better design:** ```javascript class UserService { constructor(userRepository, passwordHasher, emailService, logger) { this.userRepository = userRepository; this.passwordHasher = passwordHasher; this.emailService = emailService; this.logger = logger; } async createUser(userData) { // Validate using dedicated validator this.validateUserData(userData); // Hash password through injected service const hashedPassword = await this.passwordHasher.hash(userData.password); // Persist through repository pattern const user = await this.userRepository.create({ ...userData, password: hashedPassword }); // Async operations don't block return this.sendWelcomeEmail(user).catch(err => this.logger.error('Failed to send welcome email', { userId: user.id, err }) ); this.logger.info('User created', { userId: user.id }); return user; } validateUserData(userData) { const required = ['email', 'name', 'password']; const missing = required.filter(field => !userData[field]); if (missing.length > 0) { throw new ValidationError(`Missing required fields: ${missing.join(', ')}`); } } async sendWelcomeEmail(user) { await this.emailService.send({ to: user.email, template: 'welcome', data: { name: user.name } }); } } ``` **Second-pass checklist:** - [ ] Follows SOLID principles - [ ] Proper separation of concerns - [ ] Testable design (dependency injection) - [ ] Consistent with existing codebase patterns - [ ] Appropriate abstraction levels - [ ] Clear responsibilities ### Pass 3: Readability and Maintainability The final pass ensures the code is easy to understand and maintain. This is where you polish. ```python # AI-generated code - functionally correct but hard to read def process_orders(orders): result = [] for o in orders: if o['status'] == 'pending' and o['total'] > 100: o['priority'] = 'high' o['discount'] = o['total'] * 0.1 if o['customer']['vip'] else 0 result.append(o) elif o['status'] == 'pending': o['priority'] = 'normal' o['discount'] = 0 result.append(o) return result ``` **Readability improvements:** ```python from dataclasses import dataclass from typing import List from decimal import Decimal @dataclass class Order: order_id: str status: str total: Decimal customer: 'Customer' priority: str = 'normal' discount: Decimal = Decimal('0') class OrderProcessor: """Processes pending orders and assigns priority levels.""" HIGH_VALUE_THRESHOLD = Decimal('100') VIP_DISCOUNT_RATE = Decimal('0.1') def process_pending_orders(self, orders: List[Order]) -> List[Order]: """Filter pending orders and assign priority with applicable discounts. Args: orders: List of all orders to process Returns: List of pending orders with priority and discount applied """ pending_orders = self._filter_pending_orders(orders) return [self._assign_priority_and_discount(order) for order in pending_orders] def _filter_pending_orders(self, orders: List[Order]) -> List[Order]: return [order for order in orders if order.status == 'pending'] def _assign_priority_and_discount(self, order: Order) -> Order: """Assign priority level and calculate VIP discount if applicable.""" order.priority = self._calculate_priority(order) order.discount = self._calculate_discount(order) return order def _calculate_priority(self, order: Order) -> str: """High priority for orders above threshold, normal otherwise.""" return 'high' if order.total > self.HIGH_VALUE_THRESHOLD else 'normal' def _calculate_discount(self, order: Order) -> Decimal: """Apply VIP discount rate to high-value orders for VIP customers.""" if order.customer.is_vip and order.total > self.HIGH_VALUE_THRESHOLD: return order.total * self.VIP_DISCOUNT_RATE return Decimal('0') ``` **Third-pass checklist:** - [ ] Clear, descriptive naming - [ ] Proper documentation ([doc-generation](/lessons/doc-generation)) - [ ] Magic numbers extracted to constants - [ ] Complex logic broken into named functions - [ ] Consistent formatting - [ ] Removed commented-out code and debug statements ## Refactoring Patterns for AI Code Certain refactoring patterns appear repeatedly when improving AI-generated code. Recognize these opportunities: ### Pattern 1: Extract Configuration AI often hardcodes values that should be configurable. ```typescript // AI-generated - hardcoded values async function fetchUserData(userId: string) { const response = await fetch( `https://api.example.com/users/${userId}`, { timeout: 5000, retries: 3 } ); return response.json(); } // Refactored - externalized configuration interface ApiConfig { baseUrl: string; timeout: number; maxRetries: number; } class UserApiClient { constructor(private config: ApiConfig) {} async fetchUser(userId: string) { const url = `${this.config.baseUrl}/users/${userId}`; return this.fetchWithRetry(url); } private async fetchWithRetry(url: string) { // Retry logic using this.config.maxRetries } } ``` ### Pattern 2: Replace Conditionals with Polymorphism AI loves long if-else chains. Often, these should be polymorphic designs. ```python # AI-generated conditional mess def calculate_shipping(order): if order.type == 'standard': return order.weight * 0.5 elif order.type == 'express': return order.weight * 1.5 + 10 elif order.type == 'overnight': return order.weight * 2.5 + 25 else: raise ValueError('Unknown order type') # Refactored with strategy pattern from abc import ABC, abstractmethod class ShippingStrategy(ABC): @abstractmethod def calculate(self, weight: float) -> float: pass class StandardShipping(ShippingStrategy): def calculate(self, weight: float) -> float: return weight * 0.5 class ExpressShipping(ShippingStrategy): def calculate(self, weight: float) -> float: return weight * 1.5 + 10 class OvernightShipping(ShippingStrategy): def calculate(self, weight: float) -> float: return weight * 2.5 + 25 class Order: def __init__(self, weight: float, shipping: ShippingStrategy): self.weight = weight self.shipping = shipping def calculate_shipping_cost(self) -> float: return self.shipping.calculate(self.weight) ``` ### Pattern 3: Consolidate Duplicate Logic AI might generate similar code in multiple places because each prompt lacks context from previous generations. ```javascript // AI generated these in separate prompts - notice the duplication function validateEmail(email) { const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return regex.test(email); } function validateUserInput(data) { if (!data.email) return false; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(data.email); } function checkEmailFormat(emailAddress) { const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return pattern.test(emailAddress); } // Refactored - single source of truth class Validator { static EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; static isValidEmail(email) { return email && this.EMAIL_PATTERN.test(email); } } function validateUserInput(data) { return Validator.isValidEmail(data.email); } ``` ## Building a Review Checklist Create a standardized checklist for your team. This ensures consistency and catches common AI-generated code issues. Here's a starter template: ```markdown ## AI Code Review Checklist ### Security & Safety - [ ] No SQL injection vulnerabilities (parameterized queries used) - [ ] Input validation on all external data - [ ] No hardcoded secrets or credentials - [ ] Proper authentication and authorization - [ ] Safe handling of user-uploaded files - [ ] No eval() or similar dangerous functions ### Correctness - [ ] Edge cases handled (null, empty, boundary values) - [ ] Error handling appropriate for context - [ ] No off-by-one errors in loops - [ ] Async operations properly awaited - [ ] Resource cleanup in finally blocks ### Testing - [ ] Code is testable (no tight coupling) - [ ] Tests cover happy path and edge cases - [ ] No reliance on external services in unit tests - [ ] Tests are deterministic ### Design - [ ] Single Responsibility Principle followed - [ ] Dependencies injected, not hardcoded - [ ] Appropriate abstraction level - [ ] Consistent with existing patterns - [ ] No premature optimization ### Maintainability - [ ] Clear, descriptive names - [ ] No magic numbers or strings - [ ] Complex logic documented - [ ] No commented-out code - [ ] Consistent code style ``` Adapt this to your stack and team standards. As discussed in [team-workflows](/lessons/team-workflows), sharing this checklist ensures everyone reviews AI code consistently. ## Automated Quality Gates Don't rely solely on manual reviews. Automate what you can: ```yaml # .github/workflows/ai-code-quality.yml name: AI Code Quality Checks on: [pull_request] jobs: quality-gates: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 # Detect common AI code smells - name: Check for hardcoded secrets run: | if grep -r "api_key.*=.*'.*'" src/; then echo "Hardcoded secrets detected" exit 1 fi # Complexity analysis - name: Cyclomatic complexity run: | radon cc src/ -a -nb radon cc src/ -nc 10 # Fail if complexity > 10 # Security scanning - name: Security scan run: bandit -r src/ -ll # Test coverage minimum - name: Coverage check run: | pytest --cov=src --cov-fail-under=80 ``` As covered in [quality-control](/lessons/quality-control), automated checks catch issues before human review. ## When to Refactor vs. Regenerate Sometimes it's faster to improve your prompt and regenerate rather than manually refactoring. Use this decision framework: **Refactor when:** - The core logic is sound but needs polish - It's a small, localized change - You need to learn the codebase better - The issue is about naming or structure **Regenerate when:** - The fundamental approach is wrong - You're fixing the third issue in the same code - The code doesn't match your architecture - You can articulate what you need in a better prompt **Example regeneration:** Instead of refactoring messy code: ``` # Better prompt for regeneration "Create a UserService class that: - Uses dependency injection for database and email services - Validates input using a separate Validator class - Returns domain objects, not database records - Handles errors with custom exceptions - Follows our repository pattern (see src/repositories/BaseRepository.ts) - Matches the style in src/services/ProductService.ts" ``` See [review-refactor](/lessons/review-refactor) for deeper strategies on improving AI-generated code. ## Managing Refactoring Debt Not every AI-generated code block needs immediate refactoring. Prioritize based on: 1. **Risk**: Security issues and bugs first 2. **Frequency**: Code that changes often needs better structure 3. **Visibility**: Public APIs before internal utilities 4. **Impact**: Core business logic before peripheral features Tag code for future improvement: ```python # TODO(ai-refactor): Extract configuration to settings file # Priority: Medium | Created: 2024-01-15 | Issue: #234 API_ENDPOINT = "https://api.example.com" TIMEOUT = 5000 ``` As discussed in [managing-tech-debt](/lessons/managing-tech-debt), track these items systematically so they don't accumulate. ## Practice Exercise: Review This AI Code Here's AI-generated code with multiple issues. Practice identifying problems across all three review passes: ```python def get_user_orders(user_id): # Get user from database import sqlite3 conn = sqlite3.connect('app.db') cursor = conn.cursor() cursor.execute(f"SELECT * FROM users WHERE id = {user_id}") user = cursor.fetchone() if user: # Get orders cursor.execute(f"SELECT * FROM orders WHERE user_id = {user_id}") orders = cursor.fetchall() # Calculate total total = 0 for order in orders: total = total + order[3] # price column # Apply discount if total > 1000: discount = total * 0.1 else: discount = 0 result = { 'user': user, 'orders': orders, 'total': total, 'discount': discount, 'final': total - discount } return result ``` **Issues to find:** - Pass 1 (Correctness): SQL injection, no connection closing, magic number index - Pass 2 (Design): Mixed concerns, no error handling, tight coupling - Pass 3 (Readability): Poor naming, magic numbers, missing documentation Compare your findings with the checklists above. ## Continuous Improvement Loop The best vibe coders treat each review as a learning opportunity: 1. **Track patterns**: Note recurring issues in AI-generated code 2. **Update prompts**: Build a library of prompts that avoid common problems 3. **Share learnings**: Document team patterns and anti-patterns 4. **Refine checklists**: Add team-specific items based on your stack 5. **Measure quality**: Track metrics like bug rate, review time, and refactoring frequency This feedback loop, combined with [agentic-optimization](/lessons/agentic-optimization) strategies, continuously improves your AI-assisted development process. ## Key Takeaways Reviewing AI-generated code isn't optional—it's where the real engineering happens: - Use three-pass reviews: correctness, design, then readability - Build and maintain team-specific review checklists - Recognize common refactoring patterns in AI code - Automate quality gates to catch issues early - Know when to refactor vs. regenerate with better prompts - Treat review as a learning opportunity to improve future prompts The developers who excel at vibe coding aren't those who generate the most code—they're those who most effectively review, refactor, and improve what AI produces. Master these patterns, and you'll transform AI from a code generator into a true productivity multiplier. Next, explore [testing-strategies](/lessons/testing-strategies) to learn how to validate AI-generated code systematically, or dive into [performance-optimization](/lessons/performance-optimization) to make that code production-ready.