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
getdoes 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:
| Layer | What it is | Where |
|---|---|---|
| Smells | Symptoms that suggest the structure is hurting you | Bloaters, OO abusers, Change preventers, Dispensables, Couplers |
| Techniques | Named, safe transformations that remove a smell | Composing methods, Moving features, Organizing data, Simplifying conditionals, Simplifying method calls, Generalization |
| Patterns | Target designs you may refactor toward when a structure keeps recurring | Design 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
// 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
- Read Technical debt for why code drifts toward a mess.
- Read When to refactor to decide whether now is the moment.
- Read How to refactor for the step-by-step discipline.
See also
- Technical DebtTechnical debt is the extra cost every future change pays because of shortcuts and drift in today's code. Learn its causes and how to pay it down.
- When to RefactorRefactor right before you change code, while fixing a bug and during review. Skip it when the code is due for deletion or has no tests.
- How to RefactorRefactor in small named steps, run tests after each one, never mix in behavior changes, and stop when the code is clean enough for the task at hand.
- Extract MethodMove a coherent fragment of a long function into its own well-named function and call it from the original place.
- Long MethodA function that does several jobs in a row and has grown too long to read, name or test as one unit.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.