Refactoring Patterns
Apply systematic refactoring: extract function/class, replace conditional with polymorphism, strangler fig pattern for legacy, and refactoring with tests as a safety net.
Refactoring is the disciplined technique of restructuring code without changing its external behavior. Effective refactoring requires a comprehensive test suite as a safety net, small incremental steps, and understanding which code smells map to which refactoring patterns.
Refactor with tests. Each refactoring step should be small enough to be safe without a full test run. Apply the Strangler Fig pattern for large legacy rewrites - never a big-bang rewrite. Name things clearly as the first refactoring.
Workflow
- Identify code smells before refactoringCode smells are signals - not problems themselves, but indicators of where refactoring will pay off.
- Apply Extract Function and Extract ClassThese are the most common refactorings - they reduce function length, improve naming, and enable reuse.
- Replace conditionals with polymorphismSwitch/if-else chains on type fields are the most common smell for polymorphism refactoring.
- Use Strangler Fig for legacy migrationBig-bang rewrites fail 90% of the time. Strangler Fig migrates incrementally with the old and new systems running in parallel.
- Refactor safely with the test-edit-test cycleWithout tests, refactoring is gambling. Small steps with immediate test feedback make refactoring safe.
Output Format
Code smell identification checklist, Extract Function/Class applied to specific smell, polymorphism replacing switch on type, Strangler Fig plan for legacy migration, test-before-refactor discipline.
Examples
Replace conditional: `// before: const label = status === "active" ? "Active" : status === "pending" ? "Pending" : "Inactive"; // after: const STATUS_LABELS: Record<Status, string> = { active: "Active", pending: "Pending", inactive: "Inactive" }; const label = STATUS_LABELS[status]`Extract function: `// before: long procesPayment() with inline tax calculation // after: function procesPayment() { const tax = calculateTax(order); ... } function calculateTax(order: Order): number { return order.subtotal * TAX_RATES[order.country] }`Parameter object: `// before: function createUser(name, email, phone, address, role, planId) // after: function createUser(params: CreateUserParams): User // params = { name, email, phone, address, role, planId }`Edge Cases & Gotchas
- Legacy code with no tests: before refactoring legacy code with no tests, write characterization tests (golden master) that capture the current behavior, even if the behavior is wrong - this prevents accidental behavior changes
- Refactoring across PRs: very large refactorings should be split across multiple PRs. Use feature flags to merge refactored code that isn't active yet
- Semantic naming: "renaming" is refactoring too - prioritize clear names before complex structural changes. `calculateOrderTax` > `calcTax` > `ct`
When to Break These Rules
- When under extreme time pressure (production outage, critical deadline), fix the bug in the smelly code rather than refactoring first. Note the refactoring debt and schedule it explicitly.
Reference Files
Refactoring by Martin Fowler: https://refactoring.com/Catalog of refactorings: https://refactoring.com/catalog/Strangler Fig pattern: https://martinfowler.com/bliki/StranglerFigApplication.htmlWorking Effectively with Legacy Code (book): Feathers, 2004