| name | refactor-patterns |
| description | Apply systematic refactoring patterns — strangler fig for legacy migration, seam detection, extract method/class, introduce parameter object, replace conditional with polymorphism, and safe refactoring sequencing. Use when asked to "refactor this", "clean up legacy code", "this is unmaintainable", "too many if statements", "extract this logic", or "how do I migrate without breaking everything". Do NOT use for: full rewrites — that is an architecture decision, not a refactoring task.
|
| origin | yamtam-original |
| license | MIT © 2025 Vũ Văn Tâm |
| version | 1.0.0 |
| compatibility | Language-agnostic patterns. Examples in Python/TypeScript. |
When to Use
- Use when: a function is > 40 lines or does more than one thing
- Use when: a class has more than one reason to change
- Use when: adding a feature requires touching 10 different files
- Use when: migrating from a legacy system to a new one incrementally
- Do NOT use for: style-only changes with no behavior — that is formatting, not refactoring
Core Rule: Refactoring Is Not Rewriting
Refactoring = changing structure without changing behavior. Every step must be testable.
Safe sequence:
- Write characterization tests first (capture current behavior, even if wrong)
- Refactor in small, verified steps
- Run tests after every step — a failing test means you changed behavior
- Commit each working step — enables bisect if something breaks
Pattern Reference
Extract Method
When: a code block has a comment explaining what it does, or does one distinct thing.
def process_order(order):
if not order.items: raise ValueError("empty")
if order.total < 0: raise ValueError("negative total")
db.save(order)
email.send(order.user, "Your order was placed")
def process_order(order):
_validate_order(order)
db.save(order)
_notify_user(order)
def _validate_order(order): ...
def _notify_user(order): ...
Extract Class
When: a class has two distinct groups of data/methods that don't talk to each other.
OrderService has:
- order_items, calculate_total(), apply_discount() ← pricing concern
- user_address, validate_shipping(), shipping_cost() ← shipping concern
Split into:
PricingCalculator (total, discount)
ShippingCalculator (address, cost)
OrderService (orchestrates both)
Introduce Parameter Object
When: a function takes ≥ 4 parameters that logically belong together.
def create_report(start_date, end_date, region, currency, include_tax):
@dataclass
class ReportOptions:
start_date: date
end_date: date
region: str
currency: str = "USD"
include_tax: bool = True
def create_report(options: ReportOptions):
Replace Conditional with Polymorphism
When: a switch/if-elif chain dispatches on a type field.
def render(shape):
if shape.type == "circle": return render_circle(shape)
elif shape.type == "rect": return render_rect(shape)
elif shape.type == "line": return render_line(shape)
class Circle:
def render(self): ...
class Rect:
def render(self): ...
Strangler Fig (Legacy Migration)
Incrementally replace a legacy system without a big-bang rewrite.
Phase 1 — Intercept
All requests → Facade → Legacy System (100%)
Facade logs which features are called how often
Phase 2 — Divert one feature
Facade checks: is this request for Feature X?
Yes → New System (Feature X only)
No → Legacy System
Phase 3 — Divert more features (repeat)
Each iteration: extract one feature, verify, shift traffic
Phase 4 — Retire
When 100% diverted → remove Legacy System → remove Facade
Rules:
- Never run both systems writing to the same DB — divergence is catastrophic
- Dual-write period: write to both, read from new, verify consistency before cutting read
- Keep legacy running until 0% traffic — don't delete before verifying
Seam Detection
A seam is a place where you can change behavior without touching the code being changed.
Find seams before refactoring — they are where tests and extractions plug in.
Types of seams:
- Object seam: inject a dependency rather than constructing it inside the class
- Link seam: replace a module/library at link time (dependency injection framework)
- Preprocessing seam: conditionally compile or load different code
class OrderService:
def place(self, order):
EmailClient().send(...)
class OrderService:
def __init__(self, email_client: EmailClient):
self._email = email_client
def place(self, order):
self._email.send(...)
Refactoring Sequence for Large Functions
For a function > 100 lines with no tests:
- Write a characterization test (golden master): capture current output for known inputs
- Extract all inline comments into method names (Extract Method)
- Eliminate magic numbers → named constants
- Eliminate repeated logic → helper methods
- Group data that moves together → parameter objects or small classes
- If conditionals on type → introduce polymorphism
- Verify tests still pass after each step
Anti-Fake-Pass Rules
Before claiming a refactor is done, you MUST show:
Reference: gates/anti-fake-pass-gate.md