Refactoring
The problem
Code that works can still be badly structured — hard to change safely, full of duplicated logic, tightly coupled in ways that make the next feature harder than it should be — and that structural debt only gets worse if it's never deliberately paid down through safe, behavior-preserving restructuring.
Why now
Coupling and cohesion already gave the vocabulary for judging structure; refactoring is the disciplined activity of actually improving it without changing behavior, and writing effective tests already established the safety net (a test suite proving behavior is unchanged) that makes doing this without fear possible.
Mental model
Refactoring is restructuring code's internals while its external behavior stays provably identical — 'provably' meaning a test suite passes before and after, unchanged, giving you the confidence to change how something works without gambling on whether it still works. It's coupling-and-cohesion's judgment turned into action, one safe, verified step at a time instead of a risky rewrite.
Requires
- Writing Effective TestsEngineering Practices
Refactoring's entire safety guarantee comes from a passing test suite proving behavior hasn't changed; without tests already in place, 'refactoring' is indistinguishable from 'possibly breaking things and hoping not.'
- Coupling & CohesionSoftware Architecture
Refactoring needs a target to move toward — better cohesion, lower coupling — and that vocabulary already covered is exactly what turns 'this code feels messy' into a specific, actionable direction for improvement.
Unlocks
Used in
- Every long-lived codebase's ongoing maintenance and structural improvement
- Extract-function, extract-module, and similar named refactoring patterns
- Preparing a section of code for a new feature by first cleaning up its structure
- Large-scale strangler-fig migrations, refactoring a system piece by piece behind a stable interface
Projects
- Take a piece of duplicated logic scattered across three places, write tests capturing its current behavior, then extract it into one shared function and confirm the tests still pass
- Refactor a large, low-cohesion function into several smaller, well-named ones, verifying behavior is unchanged via an existing or newly written test suite
Examples
- Extracting three copies of near-identical validation logic into one shared function, verified by tests, eliminates the risk of the three copies silently drifting apart over time
- A large function refactored into five smaller, named functions doesn't change what the code does, only how easy it is to read, test, and change safely
Resources
Mastery checklist
- I can refactor code while keeping an existing test suite green throughout
- I can extract duplicated logic into a single, well-named shared function
- I can break a low-cohesion function into smaller, focused ones
- I can explain why refactoring without tests is much riskier than refactoring with them