Skip to content
aipatterns.dev

Guide

What Is Refactoring

Refactoring changes the internal structure of code in small, behavior-preserving steps so it becomes cheaper to read and change.

Also known as
Refactoring basics

Definition

Refactoring is a change to the structure of code that leaves its observable behavior unchanged. Inputs that produced a result before still produce the same result after. The only thing that moves is how the code is arranged: names, boundaries, where data lives, how responsibilities split.

If a change alters what the program does, it is not a refactoring, no matter how small it is. Fixing a bug, adding a field to an API response or changing an error message are behavior changes. They can happen next to a refactoring, but never inside the same step.

The word covers two things:

  • A refactoring (noun): one named, mechanical transformation such as Extract Method or Replace Temp with Query.
  • Refactoring (activity): a sequence of those transformations applied toward a goal, such as "make the pricing rules testable".

What counts as clean code

Refactoring aims at code that is cheap to change. That usually means:

  • Names say what things do. A reader can guess a function's behavior from its signature.
  • Each unit has one reason to change. Pricing rules and email formatting do not share a class.
  • No duplication of knowledge. One rule lives in one place, so a fix lands once.
  • Small surface area. Functions take few arguments, classes expose few public members.
  • Tests pass and are fast. Clean code that cannot be verified is a guess.
  • No surprises. Queries do not modify state; a method named get does not write to the database.

Clean code is not short code. A three-line function full of clever chaining can be harder to change than ten plain lines.

What refactoring is not

  • Not a rewrite. A rewrite replaces code wholesale and has to rediscover every edge case. Refactoring keeps the program working at every step.
  • Not optimization. Performance work often makes code harder to read. It is a separate goal with separate verification (benchmarks as well as tests).
  • Not adding features. Kent Beck's "two hats" rule applies: at any moment you are either adding behavior or restructuring, never both.
  • Not reformatting. Running a formatter is harmless, but it does not change structure. Keep formatter churn in its own commit so it does not hide real changes in a diff.

Why it matters for agents

An AI agent reads code it did not write, in a context window that is always too small. Structure decides how much an agent must load to make a correct change:

  • A 400-line method forces the agent to hold the whole thing in context. Five named 80-line methods let it load one.
  • Duplicated logic means the agent patches one copy and misses the other three. See Duplicate Code and Shotgun Surgery.
  • Primitive values with implicit meaning (status: 3) lead to plausible but wrong code. See Primitive Obsession.

Refactoring before a feature change often makes the feature change smaller and safer. That is the main reason to do it.

The vocabulary

This reference organizes refactoring into three layers:

LayerWhat it isWhere
SmellsSymptoms that suggest the structure is hurting youBloaters, OO abusers, Change preventers, Dispensables, Couplers
TechniquesNamed, safe transformations that remove a smellComposing methods, Moving features, Organizing data, Simplifying conditionals, Simplifying method calls, Generalization
PatternsTarget designs you may refactor toward when a structure keeps recurringDesign patterns

A smell page names the techniques that treat it. A technique page names the smells it fixes. Follow those links rather than guessing.

A small example

TypeScript
// Before: one function computes, formats and decides.
function invoiceLine(item: { qty: number; unitCents: number; taxable: boolean }) {
  const base = item.qty * item.unitCents;
  const tax = item.taxable ? Math.round(base * 0.2) : 0;
  return `${item.qty} x ${(item.unitCents / 100).toFixed(2)} = ${((base + tax) / 100).toFixed(2)}`;
}

// After: same output, three named pieces.
const VAT_RATE = 0.2;
const lineTotal = (i: { qty: number; unitCents: number; taxable: boolean }) =>
  i.qty * i.unitCents + (i.taxable ? Math.round(i.qty * i.unitCents * VAT_RATE) : 0);
const money = (cents: number) => (cents / 100).toFixed(2);

function invoiceLineRefactored(i: { qty: number; unitCents: number; taxable: boolean }) {
  return `${i.qty} x ${money(i.unitCents)} = ${money(lineTotal(i))}`;
}

The steps were Replace Magic Number with Symbolic Constant, then Extract Method twice. A test that compares both versions on a handful of inputs confirms nothing changed.

Next

See also

Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.