Software Engineering7 min read

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.

refactoringclean-codepatternsengineering

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

  1. Identify code smells before refactoringCode smells are signals - not problems themselves, but indicators of where refactoring will pay off.
  2. Apply Extract Function and Extract ClassThese are the most common refactorings - they reduce function length, improve naming, and enable reuse.
  3. Replace conditionals with polymorphismSwitch/if-else chains on type fields are the most common smell for polymorphism refactoring.
  4. 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.
  5. 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.html
  • Working Effectively with Legacy Code (book): Feathers, 2004

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.