Refactoring Patterns Framework
A disciplined approach to improving the internal structure of existing code without changing its observable behavior. Every refactoring follows the same loop: verify tests pass, apply one small structural change, verify tests still pass.
Core Principle
Refactoring is not rewriting. It is a sequence of small, behavior-preserving transformations, each backed by tests. You never change what the code does — only how it is organized. Big-bang rewrites fail because they combine structural change with behavioral change, making it impossible to know which broke things.
The foundation: Bad code is a natural consequence of delivering under time pressure, not a character flaw. Code smells are objective signals of degraded structure; the smell catalog tells you where to look, and the refactoring catalog tells you what to do.
Scoring
Goal: 10/10. Score structural quality by how many of the eight Quick Diagnostic rows pass — score = round(passed / 8 × 10), adjusting down when a single smell is severe. Bands:
- 9-10: no obvious smells remain, each function does one thing, names reveal intent, duplication is eliminated, conditionals use polymorphism where apt, and tests cover the refactored paths.
- 5-6: a few smells remain (a Long Method, some duplication) but structure is mostly sound.
- ≤3: pervasive smells — tangled conditionals, God classes, duplication everywhere — or no tests to refactor safely.
Always state the current score, name the smells driving it down, and list the specific refactorings needed to reach 10/10.
The Refactoring Patterns Framework
Six areas of focus for systematically improving code structure:
1. Code Smells as Triggers
Core concept: Code smells are surface indicators of deeper structural problems — not bugs, but signals that the design makes code harder to understand, extend, or maintain. Each smell maps to named refactorings that fix it.
Why it works: Named smells give teams objective criteria instead of subjective "I don't like this" — "This is Feature Envy" points directly at the fix.
Key insights:
- Smells cluster into five families: Bloaters, Object-Orientation Abusers, Change Preventers, Dispensables, Couplers
- Long Method is the most common smell; Duplicate Code is the most expensive
- A method that needs a comment to explain what it does is a smell — extract and name the block instead
- Shotgun Surgery (one change, many classes) and Divergent Change (one class, many reasons to change) are opposite signals of misplaced responsibilities
- Primitive Obsession — raw strings/ints instead of small domain objects — spreads errors and duplication
Code applications:
See references/smell-catalog.md [blocked] when you need to name a smell and its fix — all five families (Bloaters, OO Abusers, Change Preventers, Dispensables, Couplers) with detection heuristics and the refactoring each maps to.
2. Composing Methods
Core concept: Most refactoring starts here: break long methods into smaller, well-named pieces that read like prose — high-level steps delegating to clearly named helpers.
Why it works: Short methods with intention-revealing names eliminate comments, make bugs obvious at a glance, and enable reuse; a method call costs nothing to read when the name says everything.
Key insights:
- Extract Method is the single most important refactoring — master it first
- Urge to write a comment? Extract the block and use the comment as the method name
- Inline Method when the body is as clear as the name — indirection without value is noise
- Replace Temp with Query for computed values used in multiple places; Split Temporary Variable when one temp serves two purposes
- Replace Method with Method Object when locals are too tangled to extract — they become fields
Code applications:
See references/composing-methods.md [blocked] when applying any method-level transformation — step-by-step mechanics and before/after code for Extract/Inline Method, Extract/Inline Variable, Replace Temp with Query, Split Temporary Variable, and Replace Method with Method Object.
3. Moving Features Between Objects
Core concept: The key OO design decision is where responsibilities live. When Feature Envy, excessive coupling, or unbalanced class sizes show a method or field is in the wrong class, move it where it belongs.
Why it works: A method placed away from the data it uses creates invisible cross-class dependencies, so one logical change ripples across many files — Shotgun Surgery. Co-locating method and data confines the change to one class.
Key insights:
- Move Method when a method uses more of another class's features than its own; Move Field likewise
- Extract Class when one class does two things — split along the axis of change; Inline Class when one does too little
- Hide Delegate enforces the Law of Demeter; Remove Middle Man undoes it when forwarding becomes the whole class
- Resolve that tension case by case: hide the delegate when the chain is unstable, remove the middle man when it's pure forwarding
Code applications:
See references/moving-features.md [blocked] when deciding where a responsibility belongs — mechanics for Move Method/Field, Extract/Inline Class, Hide Delegate, and Remove Middle Man.
4. Organizing Data
Core concept: Raw data — magic numbers, exposed fields, integer type codes — creates subtle bugs and scatters domain knowledge. Replace primitives with objects that encapsulate behavior and enforce invariants.
Why it works: An int amount has no rounding rules or currency code; a Money object encapsulates all of it, so business rules live in one place and the type system catches errors at compile time.
Key insights:
- Replace Magic Number with Symbolic Constant — the simplest data refactoring; it names intent
- Replace Data Value with Object cures Primitive Obsession (
EmailAddress,Money,Temperature) - Encapsulate Field and Encapsulate Collection — never expose raw fields or mutable internal lists
- Replace Type Code with Subclasses when the code affects behavior; with Strategy when subclassing is impractical
- Change Value to Reference when you need identity semantics (one shared
Customer, not copies)
Code applications:
See references/organizing-data.md [blocked] when replacing primitives with objects — mechanics for Replace Data Value with Object, Change Value to Reference, Replace Magic Number, Encapsulate Field/Collection, and the Replace Type Code variants.
5. Simplifying Conditional Logic
Core concept: Deeply nested if/else trees, long switches, and scattered null checks are the hardest code to read and the most bug-prone. Named refactorings decompose, consolidate, and replace conditionals with clearer structures.
Why it works: A six-branch conditional forces readers to simulate every path mentally; well-named extracted branches are self-documenting, and polymorphism eliminates whole categories of "forgot this case" bugs.
Key insights:
- Decompose Conditional: extract condition, then-branch, and else-branch into named methods
- Consolidate Conditional Expression: merge conditions with the same result into one named check
- Replace Nested Conditional with Guard Clauses: handle edge cases early and return, keeping the main path unindented
- Replace Conditional with Polymorphism is the gold standard for type-based conditionals
- Introduce Special Case (Null Object) eliminates scattered
if (x == null)checks; Introduce Assertion makes assumptions fail fast
Code applications:
See references/simplifying-conditionals.md [blocked] when untangling branches — before/after examples for Decompose/Consolidate Conditional, Guard Clauses, Replace Conditional with Polymorphism, Special Case, and Assertions.
6. Safe Refactoring Workflow
Core concept: Refactoring is only safe when wrapped in tests. The workflow is mechanical: run tests (green), apply one small transformation, run tests (green), commit. If tests go red, revert — don't debug a broken refactoring.
Why it works: Small steps make the failure obvious (it was the last thing you did) and reverting costs seconds; debugging a failed big-bang rewrite costs days.
Key insights:
- Rule of Three: tolerate duplication once, note it twice, refactor on the third occurrence
- Preparatory refactoring: restructure to make the feature easy before adding it; comprehension and litter-pickup refactoring keep code improving as you read and touch it
- When NOT to refactor: rewriting is easier, no tests and adding them isn't feasible, or the code will be deleted soon
- Refactor for clarity first, then profile and optimize the measured bottleneck — clear code is easier to tune
- Branch by Abstraction and Parallel Change enable large refactorings in production without long-lived branches
Code applications:
See references/refactoring-workflow.md [blocked] before a large or risky refactoring — the full green-to-green cycle, when (not) to refactor, performance, Branch by Abstraction, and Parallel Change.
Common Mistakes
Quick Diagnostic
Further Reading
The definitive guides to improving existing code:
- "Refactoring: Improving the Design of Existing Code (2nd Edition)" by Martin Fowler
- "Working Effectively with Legacy Code" by Michael Feathers (companion for code without tests)
- "Clean Code: A Handbook of Agile Software Craftsmanship" by Robert C. Martin (complementary naming and style principles)
About the Author
Martin Fowler is Chief Scientist at Thoughtworks, a signatory of the Agile Manifesto, and author of Refactoring: Improving the Design of Existing Code (1999; 2nd edition 2018), which introduced catalog-based, named refactorings to mainstream development. His catalog underpins the automated refactoring tools in every major IDE.


