Claude Code/
Lesson

You prompted an AI, it generated a feature, you tested it, and it works. Ship it, right? But six weeks later, you need to modify that feature, and you realize you have no idea what half the code is doing or why it was written that way.

AI-generated code has a unique quality problem, it looks professional on the surface while hiding structural issues underneath.

The "it works" trap

When you write code by hand, you build a mental model as you go. When AI writes code, you skip all of that. The code appears fully formed, and your brain treats it as "done" because it runs.

// AI generated this for a shopping cart
function calculateTotal(items) {
  let total = 0;
  for (let i = 0; i < items.length; i++) {
    if (items[i].type === 'physical') {
      total += items[i].price * items[i].quantity;
      if (items[i].price * items[i].quantity > 100) {
        total -= items[i].price * items[i].quantity * 0.1;
      }
    } else if (items[i].type === 'digital') {
      total += items[i].price * items[i].quantity;
      if (items[i].price * items[i].quantity > 50) {
        total -= items[i].price * items[i].quantity * 0.15;
      }
    } else if (items[i].type === 'subscription') {
      total += items[i].price;
    }
  }
  if (total > 200) {
    total -= total * 0.05;
  }
  return Math.round(total * 100) / 100;
}

This works. But look again: magic numbers everywhere (100, 0.1, 50, 0.15, 200, 0.05), items[i].price * items[i].quantity repeated six times, and discount logic buried inside the loop. When your product manager says "change the bulk discount threshold from $100 to $75," you have to read the entire function to find the right number.

02

Common quality issues in AI code

AI produces the same categories of problems repeatedly. Once you recognize them, you can spot them in seconds.

Over-abstraction

You ask for a function that sends an email, and AI gives you a configurable notification system with a strategy pattern and a factory.

// You asked: "send a welcome email to new users"
// AI generated:
class NotificationFactory {
  static create(type, config) {
    switch (type) {
      case 'email':
        return new EmailNotification(config);
      case 'sms':
        return new SmsNotification(config);
      case 'push':
        return new PushNotification(config);
      default:
        throw new Error(`Unknown notification type: ${type}`);
    }
  }
}

// What you actually needed:
async function sendWelcomeEmail(user) {
  await emailClient.send({
    to: user.email,
    subject: 'Welcome!',
    template: 'welcome',
    data: { name: user.name }
  });
}

The factory pattern is valid, for a project that actually sends SMS and push notifications. If your app only sends emails, you inherited three classes you will never use.

Copy-paste patterns

When you ask AI to generate similar functionality multiple times, it does not remember what it already created. You end up with slightly different versions of the same logic scattered across your codebase.

// In userController.js - AI generated this
function formatDate(date) {
  const d = new Date(date);
  return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
}

// In orderController.js - AI generated this separately
function formatOrderDate(dateStr) {
  const date = new Date(dateStr);
  const month = date.getMonth() + 1;
  const day = date.getDate();
  const year = date.getFullYear();
  return `${month}/${day}/${year}`;
}

// In reportService.js - AI generated yet another version
const getFormattedDate = (d) => {
  const date = new Date(d);
  return [date.getMonth() + 1, date.getDate(), date.getFullYear()].join('/');
};

Three functions doing the exact same thing. When you need to switch to ISO format, you have to find and update all three.

Inconsistent style

AI does not maintain a consistent code style across separate prompts.

PatternFile A (AI response 1)File B (AI response 2)
Async handlingasync/await.then().catch()
Variable declarationsconst everywhereMix of let and const
String formattingTemplate literalsString concatenation
Error handlingtry/catch.catch() callback
ExportsNamed exportsDefault exports

None of these are wrong individually, but mixing them makes the codebase feel like it was written by five different people.

Magic numbers and strings

AI generates literal values directly in the code with no knowledge of your configuration system.

// AI-generated auth middleware
if (token.exp < Date.now() / 1000 + 300) {
  // what is 300? seconds? milliseconds? why this value?
}

if (attempts > 5) {
  // is 5 the right number? where did it come from?
}

if (user.role === 'admin' || user.role === 'super-admin') {
  // what if you add a new role?
}
AI pitfall
AI pulls these values from training data, common defaults from tutorials and Stack Overflow, not values chosen for your application. The number 5 for max login attempts, 300 for token refresh window, 3600 for cache TTL, these are "tutorial defaults" that may not be appropriate for your use case.
03

Technical debtWhat is technical debt?Shortcuts or compromises in code that save time now but create extra work later when you need to change or extend it. accumulates faster with AI

A developer writing code by hand might produce 200 lines per day and accumulate debt slowly. With AI, you can generate 2,000 lines per day, and accumulate ten times the debt. More code means more surface area for bugs, more duplication, and more diverging patterns.

This is not a reason to stop using AI. It is a reason to budget time for refactoringWhat is refactoring?Restructuring existing code to make it cleaner, more readable, or more efficient without changing what it does.. The developers who ship fastest with AI generate code in bursts, then clean up before moving on.

04

When to refactor vs when to leave it alone

Not everything needs refactoringWhat is refactoring?Restructuring existing code to make it cleaner, more readable, or more efficient without changing what it does.. Here is a practical framework.

SituationActionWhy
Code you will never touch againLeave itRefactoring has no future payoff
Prototype or throwaway codeLeave itIt will be deleted anyway
Code you are about to extendRefactor firstCleaner code is easier to modify safely
Code with duplicated logicRefactorDuplication leads to inconsistent behavior
Code others will readRefactorReadability is a team investment
Code with magic numbers in business logicRefactorYou will forget what those numbers mean
Code that works and is isolatedLeave itDo not fix what is not causing problems

The pragmatic rule: refactor when the cost of not refactoring is higher than the cost of refactoring. If you are about to extend the pricing code and it is a mess, clean it up first. If the avatar component works and nobody is touching it, leave it.

AI pitfall
When you ask AI to refactor, it sometimes refactors things that were fine and breaks them. Always refactor in small steps and test after each change. Never let AI do a "big bang" refactoring of an entire file at once.