Refactoring Patterns
Quick Reference
| Task |
Approach |
| Extract function/method |
Identify repeated or complex block, extract with clear name |
| Rename safely |
Find all references first, rename, run tests |
| Move file/module |
Update all imports, check for re-exports, run tests |
| Reduce duplication |
Extract shared logic only when used 3+ times |
| Migrate JS -> TS |
File by file, start with strictest config, add types gradually |
| Migrate CJS -> ESM |
Update require/module.exports to import/export, fix package.json |
| Simplify conditionals |
Replace nested if/else with early returns or guard clauses |
Safe Refactoring Workflow
- Verify tests pass before touching anything
- Make one change at a time -- don't combine refactors
- Run tests after each change to catch regressions immediately
- Commit frequently -- each refactor step gets its own commit
- Never mix refactoring with behavior changes in the same commit
Code Smell Detection
| Smell |
Symptom |
Refactoring |
| Long function |
> 30 lines or does multiple things |
Extract method |
| Duplicate code |
Same logic in 3+ places |
Extract shared function |
| Deep nesting |
> 3 levels of if/for/try |
Early returns, extract helper |
| Long parameter list |
> 4 parameters |
Introduce parameter object |
| Feature envy |
Method uses another class's data more than its own |
Move method |
| God class/module |
One file does everything |
Split by responsibility |
| Dead code |
Unreachable or unused code |
Delete it |
| Primitive obsession |
Raw strings/ints for domain concepts |
Introduce value types |
| Shotgun surgery |
One change requires editing many files |
Consolidate related logic |
| Middle man |
Class that only delegates |
Remove and call directly |
Extract Patterns
Extract Function
Before:
def process_order(order):
# validate
if not order.items:
raise ValueError("Empty order")
if order.total < 0:
raise ValueError("Negative total")
# calculate tax
tax = order.total * 0.08
if order.state == "CA":
tax = order.total * 0.0975
order.tax = tax
order.final_total = order.total + tax
After:
def process_order(order):
validate_order(order)
order.tax = calculate_tax(order.total, order.state)
order.final_total = order.total + order.tax
def validate_order(order):
if not order.items:
raise ValueError("Empty order")
if order.total < 0:
raise ValueError("Negative total")
def calculate_tax(total: float, state: str) -> float:
if state == "CA":
return total * 0.0975
return total * 0.08
Replace Nested Conditionals with Guard Clauses
Before:
def get_payment(employee):
if employee.is_active:
if employee.is_full_time:
if employee.years > 5:
return employee.salary * 1.1
else:
return employee.salary
else:
return employee.hourly_rate * employee.hours
else:
return 0
After:
def get_payment(employee):
if not employee.is_active:
return 0
if not employee.is_full_time:
return employee.hourly_rate * employee.hours
if employee.years > 5:
return employee.salary * 1.1
return employee.salary
Introduce Parameter Object
Before:
function createUser(name: string, email: string, age: number, role: string, team: string) { ... }
After:
interface CreateUserParams {
name: string;
email: string;
age: number;
role: string;
team: string;
}
function createUser(params: CreateUserParams) { ... }
Migration Patterns
JavaScript to TypeScript
- Rename
.js to .ts (one file at a time, start with leaf modules)
- Add
tsconfig.json with strict mode
- Fix type errors -- add types to function signatures first
- Use
unknown over any when the type is genuinely unclear
- Run tests after each file migration
CommonJS to ESM
| CJS |
ESM |
const x = require('x') |
import x from 'x' |
const { a } = require('x') |
import { a } from 'x' |
module.exports = x |
export default x |
module.exports.a = a |
export { a } |
__dirname |
import.meta.dirname (Node 21+) or fileURLToPath |
__filename |
import.meta.filename (Node 21+) or fileURLToPath |
Also update package.json: add "type": "module".
Class to Functional (React)
| Class |
Functional |
this.state / setState |
useState |
componentDidMount |
useEffect(..., []) |
componentDidUpdate |
useEffect(..., [deps]) |
componentWillUnmount |
useEffect cleanup return |
this.props |
Function parameters |
shouldComponentUpdate |
React.memo |
Safe Rename Workflow
- Search all references -- grep for the name across the codebase
- Check for dynamic usage -- string-based lookups, reflection, config files
- Rename in all locations -- source, tests, docs, configs
- Update imports/exports -- especially re-exports from index files
- Run full test suite
- Commit with descriptive message:
refactor: rename X to Y for clarity
When NOT to Refactor
| Situation |
Why |
| Code is being deleted soon |
Wasted effort |
| No tests exist for the code |
Refactor tests first, then code |
| Mixed with a feature change |
Do separately -- refactor first, then add feature |
| "While I'm here" improvements |
Stay focused on the task at hand |
| Premature abstraction |
Wait until pattern repeats 3+ times |
Anti-Patterns
| Don't |
Do Instead |
| Refactor and change behavior in one commit |
Separate commits: refactor first, then change behavior |
| Extract a helper used only once |
Inline is fine for single-use code |
| Create deep abstraction hierarchies |
Prefer composition and flat structures |
| Rename without searching all references |
Grep the entire codebase first |
| Refactor without passing tests |
Fix or write tests first |
| "Clean up" code you're not working on |
Only refactor code related to your current task |
1---2name: refactoring3description: Use when restructuring code without changing behavior -- extracting functions, renaming, moving files, reducing duplication, migrating between patterns (JS to TS, CJS to ESM), or addressing code smells. Covers safe refactoring workflows for any language.4---56# Refactoring Patterns78## Quick Reference910| Task | Approach |11|------|----------|12| Extract function/method | Identify repeated or complex block, extract with clear name |13| Rename safely | Find all references first, rename, run tests |14| Move file/module | Update all imports, check for re-exports, run tests |15| Reduce duplication | Extract shared logic only when used 3+ times |16| Migrate JS -> TS | File by file, start with strictest config, add types gradually |17| Migrate CJS -> ESM | Update require/module.exports to import/export, fix package.json |18| Simplify conditionals | Replace nested if/else with early returns or guard clauses |1920## Safe Refactoring Workflow21221. **Verify tests pass** before touching anything232. **Make one change at a time** -- don't combine refactors243. **Run tests after each change** to catch regressions immediately254. **Commit frequently** -- each refactor step gets its own commit265. **Never mix refactoring with behavior changes** in the same commit2728## Code Smell Detection2930| Smell | Symptom | Refactoring |31|-------|---------|-------------|32| Long function | > 30 lines or does multiple things | Extract method |33| Duplicate code | Same logic in 3+ places | Extract shared function |34| Deep nesting | > 3 levels of if/for/try | Early returns, extract helper |35| Long parameter list | > 4 parameters | Introduce parameter object |36| Feature envy | Method uses another class's data more than its own | Move method |37| God class/module | One file does everything | Split by responsibility |38| Dead code | Unreachable or unused code | Delete it |39| Primitive obsession | Raw strings/ints for domain concepts | Introduce value types |40| Shotgun surgery | One change requires editing many files | Consolidate related logic |41| Middle man | Class that only delegates | Remove and call directly |4243## Extract Patterns4445### Extract Function4647Before:48```python49def process_order(order):50 # validate51 if not order.items:52 raise ValueError("Empty order")53 if order.total < 0:54 raise ValueError("Negative total")55 # calculate tax56 tax = order.total * 0.0857 if order.state == "CA":58 tax = order.total * 0.097559 order.tax = tax60 order.final_total = order.total + tax61```6263After:64```python65def process_order(order):66 validate_order(order)67 order.tax = calculate_tax(order.total, order.state)68 order.final_total = order.total + order.tax6970def validate_order(order):71 if not order.items:72 raise ValueError("Empty order")73 if order.total < 0:74 raise ValueError("Negative total")7576def calculate_tax(total: float, state: str) -> float:77 if state == "CA":78 return total * 0.097579 return total * 0.0880```8182### Replace Nested Conditionals with Guard Clauses8384Before:85```python86def get_payment(employee):87 if employee.is_active:88 if employee.is_full_time:89 if employee.years > 5:90 return employee.salary * 1.191 else:92 return employee.salary93 else:94 return employee.hourly_rate * employee.hours95 else:96 return 097```9899After:100```python101def get_payment(employee):102 if not employee.is_active:103 return 0104 if not employee.is_full_time:105 return employee.hourly_rate * employee.hours106 if employee.years > 5:107 return employee.salary * 1.1108 return employee.salary109```110111### Introduce Parameter Object112113Before:114```typescript115function createUser(name: string, email: string, age: number, role: string, team: string) { ... }116```117118After:119```typescript120interface CreateUserParams {121 name: string;122 email: string;123 age: number;124 role: string;125 team: string;126}127128function createUser(params: CreateUserParams) { ... }129```130131## Migration Patterns132133### JavaScript to TypeScript1341351. Rename `.js` to `.ts` (one file at a time, start with leaf modules)1362. Add `tsconfig.json` with strict mode1373. Fix type errors -- add types to function signatures first1384. Use `unknown` over `any` when the type is genuinely unclear1395. Run tests after each file migration140141### CommonJS to ESM142143| CJS | ESM |144|-----|-----|145| `const x = require('x')` | `import x from 'x'` |146| `const { a } = require('x')` | `import { a } from 'x'` |147| `module.exports = x` | `export default x` |148| `module.exports.a = a` | `export { a }` |149| `__dirname` | `import.meta.dirname` (Node 21+) or `fileURLToPath` |150| `__filename` | `import.meta.filename` (Node 21+) or `fileURLToPath` |151152Also update `package.json`: add `"type": "module"`.153154### Class to Functional (React)155156| Class | Functional |157|-------|-----------|158| `this.state` / `setState` | `useState` |159| `componentDidMount` | `useEffect(..., [])` |160| `componentDidUpdate` | `useEffect(..., [deps])` |161| `componentWillUnmount` | `useEffect` cleanup return |162| `this.props` | Function parameters |163| `shouldComponentUpdate` | `React.memo` |164165## Safe Rename Workflow1661671. **Search all references** -- grep for the name across the codebase1682. **Check for dynamic usage** -- string-based lookups, reflection, config files1693. **Rename in all locations** -- source, tests, docs, configs1704. **Update imports/exports** -- especially re-exports from index files1715. **Run full test suite**1726. **Commit with descriptive message**: `refactor: rename X to Y for clarity`173174## When NOT to Refactor175176| Situation | Why |177|-----------|-----|178| Code is being deleted soon | Wasted effort |179| No tests exist for the code | Refactor tests first, then code |180| Mixed with a feature change | Do separately -- refactor first, then add feature |181| "While I'm here" improvements | Stay focused on the task at hand |182| Premature abstraction | Wait until pattern repeats 3+ times |183184## Anti-Patterns185186| Don't | Do Instead |187|-------|-----------|188| Refactor and change behavior in one commit | Separate commits: refactor first, then change behavior |189| Extract a helper used only once | Inline is fine for single-use code |190| Create deep abstraction hierarchies | Prefer composition and flat structures |191| Rename without searching all references | Grep the entire codebase first |192| Refactor without passing tests | Fix or write tests first |193| "Clean up" code you're not working on | Only refactor code related to your current task |