Refactoring = improving code without changing behavior
The definition matters: refactoring changes structure, not behavior. You make code cleaner, simpler, or more efficient while it does exactly the same thing — verified by tests. If behavior changes, that's a feature/fix, not a refactor.
The safety net: tests first
The rule: never refactor without tests (or a reliable reproduction of current behavior). Tests are what let you change structure confidently — run them before and after; green both times means behavior held. Refactoring without tests is just "changing code and hoping."
Code smells (what to fix)
- Duplication — the same logic in several places; extract it (DRY).
- Long functions / god objects — doing too much; split into focused pieces.
- Deep nesting — replace with early returns / guard clauses.
- Unclear names — rename for intent (the cheapest, highest-value refactor).
- Dead code — delete it.
- Primitive obsession / magic numbers — name constants, model concepts.
Do it in small, safe steps
Refactor incrementally: one small transformation at a time, tests green after each, commit often. A big-bang rewrite is risky and hard to review; a sequence of tiny, verified steps is safe and reversible. AI assistants are great for mechanical refactors (extract function, rename) — but read the diff and re-run tests, because they can subtly change behavior (previous chapters).
When (and when not) to refactor
Refactor when it makes the code easier to change for the task at hand — "leave it cleaner than you found it," or refactor before adding a feature so the change is easy. Don't gold-plate: refactoring for its own sake, or a risky rewrite with no tests and no reason, adds risk without value. The goal is code that's easier to understand and change — verified to behave identically.