Systematic code refactoring with risk mitigation. Trigger: When refactoring legacy code, migrating technologies, or resolving technical debt.
Refactoring legacy code, migrating technologies, and resolving technical debt with minimal risk and maximum impact.
Don't use for:
Create safety net BEFORE touching any code.
git add . && git commit -m "chore: snapshot before refactoring UserService"
git checkout -b refactor/user-service-cleanup
git tag refactor-start-$(date +%Y%m%d)
npm test -- --coverage > baseline-test-results.txt
npm run build > baseline-build-output.txt
Never refactor production code without test coverage.
// Add characterization tests first
describe('processPayment', () => {
test('calculates correct fee for $100', () => {
expect(processPayment(100, 'user-123')).toBe(103.20);
});
test('handles edge case: $0 amount', () => {
expect(processPayment(0, 'user-123')).toBe(0.30);
});
});
// Minimum: 80% unit coverage, 60% integration coverage
Make small, verifiable changes. Commit at every stable state.
// Commit 1: "Extract calculateFee() from processPayment"
function calculateFee(amount: number): number { return amount * 0.029 + 0.30; }
// Tests pass ✓
// Commit 2: "Replace magic numbers with named constants"
const STRIPE_RATE = 0.029;
const STRIPE_FIXED_FEE = 0.30;
// Tests pass ✓ — target: <300 lines per commit
Use feature flags to enable runtime switching between old/new implementations.
// ✅ CORRECT: Feature flag with gradual rollout
export const paymentService = config.featureFlags.useNewPayment
? new NewPaymentService()
: new LegacyPaymentService();
// Week 1: 5% → Week 2: 25% → Week 3: 50% → Week 4: 100% (remove flag)
Refactoring changes HOW code works, not WHAT it does.
// ✅ CORRECT: Preserve original null-on-error behavior
function getUser(id: string): User | null {
const result = findUserById(id); // Extracted to helper
return result ?? null; // Same null-on-error behavior
}
// Rule: if behavior must change, do it in a separate commit AFTER refactoring
Use automation for mechanical transformations. Save manual effort for logic improvements.
# Run jscodeshift codemod across entire codebase
npx jscodeshift -t codemod-replace-moment-with-datefns.js src/
# See references/advanced-techniques.md for full codemod script example
Lock dependencies before refactoring to prevent environment variables.
npm ci # Node.js: exact versions from package-lock.json
pip freeze > requirements-lock.txt && pip install -r requirements-lock.txt
bundle install --frozen
# Rule: refactor first, update dependencies separately
For JS→TS migration, use // @ts-check for incremental type safety without file conversion.
// tsconfig.json — enable strict mode incrementally
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}
// Phases: @ts-check → JSDoc types → .js→.ts rename → TypeScript types → strict: true
Calculate effort vs impact before starting.
Quick Wins (Prioritize First):
Medium-Term:
Long-Term:
// ❌ WRONG: All users immediately use new code (HIGH RISK)
export const paymentService = new NewPaymentService();
// ❌ WRONG: Massive refactor in one commit (500 files changed)
// Unreviewable, hard to rollback, high risk
Need to improve code?
↓
Is there 80%+ test coverage?
NO → Add tests first OR Accept debt with monitoring
YES → Continue
↓
Will code change frequently in next 6 months?
NO → Accept debt (document for future)
YES → Continue
↓
Can behavior be preserved?
NO → REWRITE (new requirements)
YES → Continue
↓
Is technology stack obsolete?
YES → REWRITE with migration pattern
NO → Continue
↓
Estimated effort?
1-4 weeks → REFACTOR (quick wins)
1-3 months → REFACTOR (medium-term)
3-12 months → REWRITE or phased REFACTOR
>12 months → Accept debt, break into phases
↓
Calculate ROI
>200% first month → REFACTOR immediately
100-200% in 3 months → REFACTOR next sprint
50-100% in 6 months → Schedule for next quarter
<50% in 6 months → Accept debt, revisit annually
// Phase 1: Add type definitions to public APIs
// Before
export function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// After Phase 1
export function calculateTotal(items: Array<{ price: number }>): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
// Phase 2: Replace any with explicit types
interface DataItem { value: number; label: string; }
function processData(data: DataItem[]): number[] {
return data.map((item) => item.value);
}
// Phase 3: Enable strict mode (tsconfig.json)
// { "compilerOptions": { "strict": true, "noImplicitAny": true } }
See references/migration-patterns.md for the full Redux ORM removal, callbacks→async/await, and Redux Classic→RTK walkthroughs.
Partial migration state: Use feature flags to run old + new code in parallel. Monitor error rates for both implementations.
Breaking changes unavoidable: Create deprecation warnings 2+ versions before removal. Document migration guide with before/after examples.
Rollback during production incident: Feature flags enable instant rollback without code deploy. Monitor metrics: error rate, latency, throughput.
Test coverage gaps: Don't refactor. Either add tests first (separate initiative) or accept tech debt with monitoring.
Circular dependencies during refactor: Indicates poor separation of concerns. Introduce dependency injection or event-driven architecture.
Performance regression: Benchmark before/after with realistic data. If regression >10%, investigate optimization or revert.
Merge conflicts during long-running refactor: Rebase frequently (daily) to stay in sync with main branch. Use git rerere to remember conflict resolutions.
Flaky tests exposed during refactor: Don't fix during refactor. Document flaky tests in separate issue. Refactor assumes stable test suite.
Refactoring reveals bugs in original code: Stop refactoring. Fix bugs first (separate commit/PR), THEN resume refactor. Never mix bug fixes with refactoring.
Team members need original code during refactor: Use feature branch + feature flag. Original code remains accessible until migration complete.
Summary of the 4 phases — see references/compliance-checklist.md for the full checklist with all gates.
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer