Refactoring
Refactoring is changing the internal structure of code without changing what it does. The inputs, outputs, and side effects stay the same; the code becomes easier to read, test, and extend. The word covers both the discipline (a sequence of small, behavior-preserving steps) and the individual moves in it (rename, extract, inline, move).
Refactoring is not rewriting, and it is not "cleanup week." Done well, it's a continuous habit woven into feature work: before adding a feature, reshape the code so the feature is easy to add, then add it. Kent Beck's summary is the best guide — make the change easy, then make the easy change.
TL;DR
- Behavior stays identical. If behavior changes, it's a feature or a fix, not a refactoring — do those in separate commits.
- Work in tiny steps, running tests after each one. A refactoring that breaks the build for an hour has gone wrong.
- Tests are the safety net. No tests? Write characterization tests first.
- Refactor for a reason — to make the next change easier or to fix a smell that's actively hurting.
- Let tools do mechanical moves. IDE rename, extract, and move refactorings are safer than hand edits.
- Separate refactoring commits from behavior commits so reviewers can trust both.
Quick Example
A function that mixes calculation, formatting, and a magic number:
Three small refactorings — extract function, replace magic number with a named constant, separate calculation from presentation — each safe on its own:
Behavior is unchanged, but adding a new country is now a one-line data change, and subtotal and withVat can be unit tested on their own.
Core Concepts
Code Smells
A smell is a surface symptom that often points to a deeper design problem. Smells are prompts to look closer, not rules.
The Core Moves
A handful of refactorings do most of the work:
- Rename — variables, functions, and types whose names lie or mumble.
- Extract Function / Variable — give a name to a chunk of logic or an expression.
- Inline Function / Variable — remove indirection that no longer earns its keep.
- Move Function / Field — put behavior next to the data it uses.
- Introduce Parameter Object — group arguments that always travel together.
- Replace Conditional with Polymorphism — turn repeated type switches into subclasses or strategy objects.
- Split Phase — separate code that does two different things in sequence (parse, then compute).
Refactoring vs Rewriting
A rewrite replaces a system with a new one built from scratch. A refactoring evolves the existing one in place. Rewrites are tempting because the old code is ugly, but the ugliness usually encodes years of bug fixes and edge cases. The strangler fig pattern — building new behavior alongside the old and routing traffic over piece by piece — is refactoring at system scale and is almost always safer than a big-bang rewrite.
When Not to Refactor
- The code works, rarely changes, and nobody needs to read it.
- You're about to delete or replace it.
- There's no way to verify behavior and no time to add tests — note it as technical debt instead.
Best Practices
Refactor Under Green Tests
Run the tests before starting. If they fail, you can't tell whether your change broke something. After each small step, run them again. Fast unit tests make this loop take seconds.
Characterize Legacy Code First
When code has no tests, write characterization tests that record what it currently does — even if that behavior looks wrong. They pin down behavior so you can refactor safely, and they document surprises you may want to fix later as a separate change.
Use the Tools
Modern IDEs perform rename, extract, move, and change-signature refactorings across a whole codebase with type awareness. They're faster and far less error-prone than search-and-replace. See IDEs & Editors.
Keep Refactoring Commits Separate
A pull request that renames forty identifiers and changes business logic is nearly impossible to review. Put structural changes in their own commits (or PRs) with messages like "Refactor: extract pricing rules, no behavior change."
Follow the Change
Refactor the code you're about to touch, not the whole codebase. The "boy scout rule" — leave the code a little better than you found it — compounds over months without needing a dedicated project.
Common Mistakes
Mixing Refactoring With Behavior Changes
When a bug appears after a combined commit, you can't tell which part caused it. Separate them.
Big-Bang Refactors on a Long-Lived Branch
A two-week branch that restructures a module will conflict with everyone else's work and is risky to merge. Use small steps merged to main, feature flags, or branch-by-abstraction instead.
Refactoring Toward a Pattern Too Early
Introducing a design pattern for a single case adds indirection with no benefit. Wait until the duplication or variation actually appears.
Treating Refactoring as a Separate Project
"We'll refactor next quarter" rarely happens, and when it does, it's disconnected from what the code needs. Refactor continuously as part of delivering features.
FAQ
What is refactoring in simple terms?
Refactoring is improving how code is organized without changing what it does. You rename things, split large functions, move logic to better places, and remove duplication, while the program's behavior stays exactly the same.
Do I need tests before refactoring?
You need some way to verify behavior. Automated tests are the best option. For legacy code without tests, write characterization tests that capture current behavior first, or limit yourself to safe automated IDE refactorings.
How is refactoring different from rewriting?
Refactoring changes existing code in small, behavior-preserving steps. Rewriting replaces it wholesale. Refactoring keeps the system working at every step; rewrites carry the risk of losing edge cases and taking far longer than planned.
How do I convince my team or manager to allow refactoring?
Tie it to delivery. Refactor the code you're changing as part of the feature estimate, and explain it as reducing the cost of this and future changes. Track recurring pain as technical debt with a business impact attached.
What's the best book on refactoring?
Martin Fowler's Refactoring (2nd edition) is the standard catalog of moves, with JavaScript examples. Michael Feathers' Working Effectively with Legacy Code covers getting untested code under test.
Related Topics
- Clean Code & Best Practices — The destination refactoring moves toward
- Technical Debt — Deciding what to refactor and when
- Design Patterns — Common shapes to refactor toward
- Unit Testing — The safety net for every refactoring
- Code Review — Reviewing structural changes effectively