Code Refactor
Overview
This skill guides systematic refactoring of one or more codebase components through three phases:
- •Research - Deep analysis of component behavior, usage patterns, and dependencies
- •Proposal - Concrete change suggestions with code samples and impact analysis
- •Test Plan - Test strategy to validate changes without regressions
Each phase produces a markdown document, creating a complete refactoring proposal that can be reviewed before implementation.
Output Structure
Ask the user for an output directory (e.g., ./docs/refactoring/ or ./proposals/).
Create a subfolder for the refactoring effort:
{output-directory}/
└── {component-name}-refactor/
├── 01-research.md # Phase 1: Component Analysis
├── 02-proposal.md # Phase 2: Change Proposals
└── 03-test-plan.md # Phase 3: Test Strategy
For multi-component refactoring, create subfolders:
{output-directory}/
└── {thread-name}-refactor/
├── component-a/
│ ├── 01-research.md
│ ├── 02-proposal.md
│ └── 03-test-plan.md
└── component-b/
├── 01-research.md
├── 02-proposal.md
└── 03-test-plan.md
Phase 1: Research
Goal: Comprehensive understanding of the component before proposing changes.
For multi-component refactoring, use parallel Explore agents to research each component simultaneously, then synthesize findings.
Step 1: Component Inventory (Use /codebase-librarian)
Start with the /codebase-librarian skill to establish baseline understanding:
- •Project foundation (language, tooling)
- •Entry points relevant to the component
- •Services the component interacts with
- •Infrastructure dependencies
- •Domain model elements involved
This provides context for the deeper component analysis that follows.
Step 2: Component Deep Dive
After the inventory, analyze the specific component(s) being refactored.
Understand how it works:
- •Core responsibilities and behavior
- •Internal structure and control flow
- •State management and data transformations
- •Error handling patterns
- •Configuration and initialization
Understand instantiation and usage:
- •Where is it instantiated?
- •How many instantiation patterns exist?
- •What parameters/dependencies are injected?
- •Is it a singleton, factory-created, or ad-hoc?
Map the surroundings:
- •Dependents (what calls this component)
- •Dependencies (what this component calls)
- •Before: What happens before invocation?
- •After: What happens after invocation?
Step 3: Call Graph
Create a visual representation of the component's call relationships:
┌─────────────────────────────────────────────────────────────┐
│ CALLERS │
├─────────────────────────────────────────────────────────────┤
│ OrderController.create() ──┐ │
│ OrderController.update() ──┼──► PaymentService │
│ CheckoutWorker.process() ──┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PaymentService │
├─────────────────────────────────────────────────────────────┤
│ processPayment() │
│ refundPayment() │
│ validateCard() │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ DEPENDENCIES │
├─────────────────────────────────────────────────────────────┤
│ StripeClient │ PaymentRepository │
│ FraudDetectionService │ EventEmitter │
└─────────────────────────────────────────────────────────────┘
Research Output Format
Write to 01-research.md:
# Research: [Component Name] **Generated**: [Date] **Scope**: [Component path/module] ## Executive Summary [2-3 paragraphs covering:] - Component's purpose and role in the system - Key findings about current implementation - Areas of concern or complexity discovered ## Component Analysis ### Core Behavior [What the component does, its responsibilities] ### Internal Structure [Classes, functions, modules that compose it] ### State & Data Flow [How data moves through the component] ## Instantiation Patterns | Location | Pattern | Parameters | Frequency | |----------|---------|------------|-----------| | `src/api/orders.ts:45` | Direct new | config, logger | 3 places | | `src/workers/checkout.ts:12` | Factory | config | 1 place | ## Dependency Analysis ### Dependents (Who calls this) | Caller | Method | Context | |--------|--------|---------| | `OrderController` | `processPayment()` | HTTP request handling | | `CheckoutWorker` | `processPayment()` | Background job | ### Dependencies (What this calls) | Dependency | Usage | Abstracted? | |------------|-------|-------------| | `StripeClient` | Payment processing | No - direct SDK | | `PaymentRepository` | Persistence | Yes - interface | ### Execution Context **Before invocation**: [What typically happens before this component is called] **After invocation**: [What happens with the result] ## Call Graph [ASCII diagram as shown above] ## Key Observations - [Notable pattern or concern 1] - [Notable pattern or concern 2] - [Areas that may need attention during refactoring]
Phase 2: Proposal
Persona: Pragmatic Senior Engineer
Mindset: Balance ideal architecture with practical constraints. Quick wins build momentum and trust. Large changes need clear justification. Every change should have a clear "why".
Proposal Structure
Order changes from quick wins to complex restructuring:
- •Quick Wins - Small, low-risk improvements (hours)
- •Medium Changes - Meaningful refactoring (days)
- •Large Restructuring - Significant architectural changes (weeks)
For Each Change
Provide:
- •Problem Statement - What's wrong and why it matters
- •Proposed Solution - Concrete approach
- •Code Samples - Before and after comparisons
- •Impact Analysis - Files affected, risks, dependencies
- •Pros and Cons - Honest trade-off assessment
Proposal Output Format
Write to 02-proposal.md:
# Refactoring Proposal: [Component Name]
**Generated**: [Date]
**Based on**: 01-research.md
## Executive Summary
[Overview of proposed changes, expected benefits, and effort distribution]
---
## Quick Wins
### 1. [Change Title]
**Problem**
[What's wrong - reference research findings]
**Solution**
[What to do]
**Before**
```typescript
// src/services/payment.ts:45-52
class PaymentService {
constructor() {
this.stripe = new Stripe(process.env.STRIPE_KEY);
}
}
After
// src/services/payment.ts:45-55
class PaymentService {
constructor(private readonly paymentGateway: PaymentGateway) {}
}
// src/ports/payment-gateway.ts (new)
interface PaymentGateway {
charge(amount: number, currency: string): Promise<ChargeResult>;
}
Impact
- •Files modified:
src/services/payment.ts,src/composition-root.ts - •Files created:
src/ports/payment-gateway.ts - •Risk: Low - constructor signature change requires updating instantiation sites
Pros
- •Enables unit testing without Stripe sandbox
- •Prepares for potential payment provider switch
Cons
- •Adds indirection layer
- •Requires updating 3 instantiation sites
Medium Changes
2. [Change Title]
[Same structure as above]
Large Restructuring
3. [Change Title]
[Same structure - may be higher level for larger efforts]
Implementation Order
Recommended sequence considering dependencies:
- •[Quick Win 1] - No dependencies
- •[Quick Win 2] - No dependencies
- •[Medium Change 1] - Depends on Quick Win 1
- •[Large Restructuring] - Depends on Medium Change 1
Risk Assessment
| Change | Risk Level | Mitigation |
|---|---|---|
| [Change 1] | Low | Existing tests cover behavior |
| [Change 2] | Medium | Add integration tests first |
| [Change 3] | High | Feature flag, gradual rollout |
---
## Phase 3: Test Plan
**Goal**: Design tests that validate refactoring without regressions.
### Analysis Steps
1. **Audit existing tests** - What's already covered?
2. **Identify gaps** - What behavior lacks test coverage?
3. **Design new tests** - Focus on integration over unit tests
4. **Minimize mocks** - Prefer real dependencies when feasible
### Test Philosophy
- **Favor integration tests** - Test real interactions between components
- **Use mocks sparingly** - Only for external services, not internal dependencies
- **Test behavior, not implementation** - Tests should survive refactoring
- **Cover edge cases discovered in research** - Use Phase 1 findings
### Test Plan Output Format
Write to `03-test-plan.md`:
```markdown
# Test Plan: [Component Name] Refactoring
**Generated**: [Date]
**Based on**: 01-research.md, 02-proposal.md
## Executive Summary
[Overview of testing strategy and coverage goals]
## Current Test Coverage
### Existing Tests
| Test File | Type | Coverage | Notes |
|-----------|------|----------|-------|
| `payment.test.ts` | Unit | Core payment flow | Heavy mocking |
| `checkout.integration.ts` | Integration | Happy path only | No error cases |
### Coverage Gaps
- [ ] Error handling in `processPayment()`
- [ ] Concurrent payment attempts
- [ ] Retry behavior after transient failures
## Test Strategy
### Integration Tests (Preferred)
Tests that exercise real component interactions:
**Test: Payment processing end-to-end**
```typescript
// tests/integration/payment.test.ts
describe('PaymentService', () => {
let service: PaymentService;
let testDb: TestDatabase;
beforeEach(async () => {
testDb = await TestDatabase.create();
service = new PaymentService(
new StripeTestGateway(), // Test mode, real API
new PaymentRepository(testDb)
);
});
it('processes payment and persists record', async () => {
const result = await service.processPayment({
amount: 1000,
currency: 'usd',
customerId: 'cust_123'
});
expect(result.status).toBe('succeeded');
const record = await testDb.payments.findById(result.id);
expect(record).toBeDefined();
expect(record.amount).toBe(1000);
});
});
Why integration over unit: Tests real database interactions, actual service coordination, and catches integration bugs that mocks would hide.
Unit Tests (When Necessary)
Use unit tests with mocks only when:
- •External API calls would be slow/costly
- •Testing pure business logic in isolation
- •Simulating error conditions difficult to reproduce
Test: Fraud detection logic
// tests/unit/fraud-detection.test.ts
describe('FraudDetector', () => {
it('flags high-risk transactions', () => {
const detector = new FraudDetector();
const result = detector.assess({
amount: 10000,
newCustomer: true,
internationalCard: true
});
expect(result.riskLevel).toBe('high');
expect(result.requiresReview).toBe(true);
});
});
Why unit here: Pure logic, no external dependencies, fast execution.
Tests Per Proposal Change
For Change 1: [Extract PaymentGateway Interface]
| Test | Type | Purpose |
|---|---|---|
gateway-contract.test.ts | Contract | Verify adapter implements interface correctly |
payment-with-mock-gateway.test.ts | Unit | Verify service works with any gateway |
New test:
// tests/contract/payment-gateway.test.ts
describe('PaymentGateway contract', () => {
const adapters = [
['Stripe', () => new StripePaymentAdapter(stripeTestClient)],
['Mock', () => new MockPaymentAdapter()],
];
test.each(adapters)('%s adapter implements contract', (name, createAdapter) => {
const adapter = createAdapter();
expect(adapter.charge).toBeDefined();
expect(adapter.refund).toBeDefined();
});
});
For Change 2: [Change Title]
[Similar structure]
Mock Usage Guidelines
Acceptable mocks:
- •External payment APIs (Stripe, PayPal)
- •Email services
- •SMS/notification services
- •Third-party APIs with rate limits
Avoid mocking:
- •Internal services (use real implementations)
- •Repositories (use test database)
- •Domain logic (test directly)
Verification Checklist
Before considering refactoring complete:
- • All existing tests pass
- • New integration tests cover changed behavior
- • Edge cases from research are tested
- • Error handling paths have coverage
- • No increase in mock usage
- • CI pipeline green
--- ## References For detailed guidance on each phase: - **Phase 1 deep dive**: See [references/research-phase.md](references/research-phase.md) for advanced research techniques - **Phase 2 patterns**: See [references/proposal-phase.md](references/proposal-phase.md) for common refactoring patterns - **Phase 3 testing**: See [references/test-plan-phase.md](references/test-plan-phase.md) for test design patterns