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

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:

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

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

References