# How to Refactor

> Refactor 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.

- kind: guide
- category: refactoring
- aliases: Refactoring workflow
- canonical: https://aipatterns.dev/guides/how-to-refactor
- inspired-by: https://refactoring.guru/refactoring/how-to

## The loop

**Make one small structural change, run the tests, commit, repeat.** Every refactoring in this reference is built to fit that loop: each technique page has a `## Steps` section where every step keeps the tests green.

1. Confirm the tests pass before you start. A red suite means you cannot tell what you broke.
2. Pick one smell and one technique. Do not start three at once.
3. Apply the technique in the smallest steps that compile.
4. Run the tests after each step.
5. Commit when green, with a message that names the technique ("Extract Method: computeLateFee").
6. Stop and reassess. The next smell may have disappeared or changed.

## What a finished refactoring looks like

- **The code is cleaner.** If a reviewer cannot see the improvement, the change was not worth the diff.
- **No new functionality.** Behavior changes go in a separate commit, before or after.
- **All tests pass.** Not "most tests", and not "the ones I touched".

If any of these fails, you did something other than refactoring.

## Before you start

### Get a safety net

Tests that cover the code you will change are a precondition. If they do not exist:

- Write **characterization tests**: call the code with representative inputs, record what it returns today, and assert on that. You are pinning behavior, not judging it.
- For code that touches I/O, capture inputs and outputs at the boundary and replay them.
- Use the type checker as a second net. In TypeScript, `tsc --noEmit` catches broken call sites after a move or rename.

### Read before you cut

Identify the smell by name. The smell page lists its treatments with "if X, then use Y" guidance. Picking the technique from the smell is more reliable than inventing a restructuring.

## During the refactoring

- **Prefer automated tools.** IDE or language-server rename, extract and move are safer than manual edits because they update every reference.
- **Change one thing per step.** Rename, then move, then extract. Not all three in one edit.
- **Keep the old path working while you build the new one.** For example, when moving a method, leave a delegating stub in the old location, migrate callers, then delete the stub ([Move Method](https://aipatterns.dev/refactoring/techniques/move-method.md), then [Inline Method](https://aipatterns.dev/refactoring/techniques/inline-method.md)).
- **Watch public interfaces.** Renaming a private helper is free. Renaming an exported function affects callers you cannot see. Deprecate first, then remove.
- **If the tests go red, revert.** Do not debug a broken refactoring forward. Undo to the last green commit and take a smaller step.

## Common sequences

| Goal | Sequence |
|---|---|
| Break up a long function | [Extract Variable](https://aipatterns.dev/refactoring/techniques/extract-variable.md) to name pieces, [Extract Method](https://aipatterns.dev/refactoring/techniques/extract-method.md) per piece, [Replace Temp with Query](https://aipatterns.dev/refactoring/techniques/replace-temp-with-query.md) where temps block extraction |
| Split a god class | [Extract Class](https://aipatterns.dev/refactoring/techniques/extract-class.md), then [Move Method](https://aipatterns.dev/refactoring/techniques/move-method.md) and [Move Field](https://aipatterns.dev/refactoring/techniques/move-field.md) into it |
| Remove a type switch | [Replace Type Code with Subclasses](https://aipatterns.dev/refactoring/techniques/replace-type-code-with-subclasses.md) or [Replace Type Code with State/Strategy](https://aipatterns.dev/refactoring/techniques/replace-type-code-with-state-strategy.md), then [Replace Conditional with Polymorphism](https://aipatterns.dev/refactoring/techniques/replace-conditional-with-polymorphism.md) |
| Shrink a parameter list | [Introduce Parameter Object](https://aipatterns.dev/refactoring/techniques/introduce-parameter-object.md) or [Preserve Whole Object](https://aipatterns.dev/refactoring/techniques/preserve-whole-object.md) |
| Merge copies of logic | [Extract Method](https://aipatterns.dev/refactoring/techniques/extract-method.md), then [Pull Up Method](https://aipatterns.dev/refactoring/techniques/pull-up-method.md) or [Form Template Method](https://aipatterns.dev/refactoring/techniques/form-template-method.md) if the copies live in sibling classes |

## When to stop

Stop when the change you came to make is straightforward. Refactoring has no natural end; every codebase can be cleaner. The task defines the finish line, not your taste.

Signs you have gone too far:

- You introduced an interface with one implementation and no test double that needs it ([Speculative Generality](https://aipatterns.dev/refactoring/smells/speculative-generality.md)).
- Readers now jump through four files to follow what used to be one function.
- The diff touches files unrelated to the task.

## Agent checklist

- [ ] The test suite was green before the first edit, and you recorded the command that runs it.
- [ ] Each step is one named technique, applied through tooling where available.
- [ ] Tests and type checks run after every step, not only at the end.
- [ ] A red result triggers a revert to the last green state, not a forward fix.
- [ ] Behavior changes are in separate commits from structural changes.
- [ ] You stopped when the target task became straightforward.
