Refactoring for AI Comprehension

Module 02: Clean Code for AI Readability | Expansion Guide

Back to Module 02

The Problem

You inherited a codebase. It works, mostly. But when you ask AI to make changes, it produces garbage. The AI doesn't understand the implicit architecture, the historical decisions, or the "don't touch that" zones.

Legacy code isn't just hard for humans - it's harder for AI. AI lacks the tribal knowledge that makes legacy code survivable.

The Core Insight

Refactoring for AI is about making implicit knowledge explicit.

What humans learn through months of working with code, AI needs to see in the code itself. The goal isn't to make code "perfect" - it's to make code self-explaining.

The Refactoring Pipeline

Phase 1: Understand Before Touching

Before any refactoring, create an AI-readable map:

# Ask AI to create a system map
"Analyze this codebase structure and create a markdown file
documenting:
1. Main entry points
2. Core domain concepts
3. Key dependencies between modules
4. Areas that look fragile or complex

Files:
[paste directory structure]
[paste key files]"

Save this as ARCHITECTURE.md. Now AI (and future you) has a map.

Phase 2: Add Strategic Comments

Don't comment what code does. Comment why it exists:

// Bad: Increments counter
counter++;

// Good: Rate limit check - prevents API abuse (incident #423)
counter++;
if (counter > RATE_LIMIT) {
  // This timeout is intentionally long - shorter values
  // caused cascade failures in production (2024-01)
  await sleep(5000);
}

Phase 3: Extract and Name

Complex logic should have names that explain intent:

// Before: Magic boolean expression
if (user.createdAt > thirtyDaysAgo && !user.verified && user.loginCount < 3) {
  sendReminder();
}

// After: Named intent
const isInactiveNewUser = (user: User): boolean => {
  const isNew = user.createdAt > thirtyDaysAgo;
  const isUnverified = !user.verified;
  const hasLowEngagement = user.loginCount < 3;
  return isNew && isUnverified && hasLowEngagement;
};

if (isInactiveNewUser(user)) {
  sendReminder();
}

Now AI can understand and modify the logic correctly.

Phase 4: Isolate the Dangerous Parts

Every codebase has "here be dragons" zones. Make them explicit:

/**
 * CRITICAL: Payment processing logic
 *
 * DO NOT MODIFY without:
 * 1. Review from payments team
 * 2. Full integration test suite passing
 * 3. Manual QA on staging
 *
 * Last audit: 2025-03-15
 * Related incidents: PAY-201, PAY-245
 */
class PaymentProcessor {
  // ...
}

AI-Readable Warning Pattern

Use consistent markers AI will recognize:
// CRITICAL: - Don't modify without human review
// LEGACY: - Works but don't understand why
// HACK: - Intentional shortcut, explain why
// TODO(importance): - Future work with priority

Safe Extraction Patterns

The Strangler Fig

Gradually replace old code by wrapping it:

// Step 1: Wrap the old function
function processOrderLegacy(order) {
  // ... 500 lines of mystery ...
}

function processOrder(order) {
  // New validation
  validateOrder(order);

  // Delegate to legacy for now
  return processOrderLegacy(order);
}

// Step 2: Incrementally move logic to new function
function processOrder(order) {
  validateOrder(order);
  calculateTotals(order);  // Extracted!

  // Smaller legacy call
  return processOrderLegacy(order);
}

// Step 3: Eventually, legacy function is empty

The Seam Test

Before extracting, add a test at the boundary:

// Add this test BEFORE refactoring
describe('processOrder', () => {
  it('produces same output as legacy for known inputs', () => {
    const testCases = loadHistoricalOrders();
    for (const order of testCases) {
      const legacyResult = processOrderLegacy(order);
      const newResult = processOrder(order);
      expect(newResult).toEqual(legacyResult);
    }
  });
});

Now you can refactor with confidence.

Dependency Analysis

Before splitting a file, understand what depends on what:

# Generate dependency graph (JavaScript)
npx madge --image deps.svg src/

# Or ask AI to analyze
"Analyze these imports and create a dependency diagram:
- Which files import from this file?
- Which files does this file import?
- Are there any circular dependencies?"

Breaking Circular Dependencies

// Problem: A imports B, B imports A
// a.ts
import { B } from './b';
export class A { b: B; }

// b.ts
import { A } from './a';
export class B { a: A; }

// Solution: Extract interface
// types.ts
export interface IA { /* ... */ }
export interface IB { /* ... */ }

// a.ts
import { IB } from './types';
export class A implements IA { b: IB; }

// b.ts
import { IA } from './types';
export class B implements IB { a: IA; }

Failure Patterns

1. Big Bang Refactor

Symptom: You tried to refactor everything at once. Now nothing works and you can't tell what broke.

Fix: One extraction at a time. Commit after each. Test before moving on.

2. Refactoring Without Tests

Symptom: "I thought I was just moving code" but behavior changed subtly.

Fix: Add characterization tests before refactoring. They capture current behavior, right or wrong.

3. Over-Abstraction

Symptom: Three new interfaces, two abstract classes, and a factory for what used to be one function.

Fix: Refactor for clarity, not for design patterns. If AI can't follow the abstraction, humans won't either.

The Golden Rule of Refactoring

If you can't explain the refactoring in one sentence, it's too big. Split it up.

Quick Reference

Before Any Refactoring:

  1. Create ARCHITECTURE.md
  2. Identify "danger zones"
  3. Add characterization tests
  4. Commit current state

Safe Extraction Checklist:

AI-Friendly Markers:

// CRITICAL: requires human review
// LEGACY: don't understand, don't touch
// HACK: intentional, see explanation
// TODO(high): future work, important