Refactoring Workflow
Complete guide to improving code structure, maintainability, and performance using agentful's systematic refactoring workflow.
Overview
The Refactoring workflow improves existing code quality without changing its external behavior. It focuses on code health, maintainability, and performance optimization.
What This Workflow Delivers
- ✅ Improved code structure and organization
- ✅ Enhanced maintainability and readability
- ✅ Performance optimizations
- ✅ Eliminated code duplication
- ✅ Better separation of concerns
- ✅ Updated tests and documentation
Typical Timeline
| Refactoring Scope | Time | Iterations |
|---|---|---|
| Function-level (single function) | 5-15 minutes | 2-5 |
| Module-level (related components) | 15-45 minutes | 5-10 |
| Feature-level (entire feature) | 45 min - 2 hours | 10-20 |
| Codebase-level (cross-cutting concerns) | 2-6 hours | 20-50 |
Prerequisites
Before starting refactoring, ensure:
1. Existing Code is Working
# All tests pass
npm test
✓ All tests passing
# Application runs without errors
npm run dev
✓ Dev server running
# No validation errors
/agentful-validate
✓ All quality gates passing2. Refactoring Goal Defined
## Refactoring Goal
**What**: Improve user authentication code structure
**Why**:
- Hard to test (tightly coupled)
- Code duplication in auth methods
- Difficult to add new auth providers
**Success Criteria**:
- [ ] Clear separation of concerns
- [ ] No code duplication
- [ ] Easy to add new auth providers
- [ ] All tests still pass
- [ ] No behavior changes3. Baseline Metrics Recorded
# Record current state
npm test -- --coverage --json > .agentful/baseline-coverage.json
npx tsc --noEmit > .agentful/baseline-types.txt
npm run lint > .agentful/baseline-lint.txt
# Record performance (if applicable)
# Bundle size, load time, etc.The Refactoring Loop
Complete Workflow Diagram
┌─────────────────────────────────────────────────────────────┐
│ START: Refactoring Goal Defined │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 1. CODE ANALYSIS │
│ • Read existing code │
│ • Identify code smells │
│ • Measure metrics (complexity, duplication) │
│ • Find opportunities for improvement │
│ • Prioritize refactoring targets │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 2. REFACTORING PLAN │
│ • Define refactoring scope │
│ • Identify specific improvements │
│ • Plan incremental changes │
│ • Ensure test coverage │
│ • Document refactoring strategy │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 3. INCREMENTAL REFACTORING │
│ • Make small, isolated changes │
│ • Run tests after each change │
│ • Preserve behavior at each step │
│ • Commit after verified step │
│ • Continue to next improvement │
└─────────────────────────────────────────────────────────────┘
↓
Tests failing?
│
┌──────────┴──────────┐
↓ ↓
┌─────────────────────────┐ ┌─────────────────────────┐
│ 4a. ROLLBACK │ │ 4b. CONTINUE │
│ • Revert change │ │ • Tests pass │
│ • Fix tests first │ │ • Verify behavior │
│ • Try again │ │ • Next improvement │
└─────────────────────────┘ └─────────────────────────┘
│ │
└──────────┬──────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 5. VALIDATION │
│ • All tests pass │
│ • No behavior changes │
│ • Quality gates pass │
│ • Performance improved or maintained │
│ • Documentation updated │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 6. COMPARISON │
│ • Compare with baseline metrics │
│ • Verify improvements │
│ • Check for regressions │
│ • Document what changed │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 7. COMPLETION │
│ • Update completion.json │
│ • Document refactoring │
│ • Update related docs │
│ • Mark refactoring complete │
└─────────────────────────────────────────────────────────────┘Step-by-Step Guide
Step 1: Code Analysis
Agent: Orchestrator (delegates to specialist agents) Time: 5-15 minutes
Analysis checklist:// Code analysis example: AuthService
// 1. Identify code smells
// src/services/auth.service.ts
export class AuthService {
// ❌ CODE SMELL: God class (too many responsibilities)
async login(email: string, password: string) { /* 50 lines */ }
async register(data: RegisterData) { /* 50 lines */ }
async resetToken(email: string) { /* 30 lines */ }
async validateToken(token: string) { /* 30 lines */ }
async refreshAccessToken(token: string) { /* 40 lines */ }
async logout(token: string) { /* 20 lines */ }
async OAuthLogin(provider: string, code: string) { /* 60 lines */ }
async verifyEmail(token: string) { /* 30 lines */ }
async sendPasswordReset(email: string) { /* 30 lines */ }
// ❌ CODE SMELL: Long method
async login(email: string, password: string) {
// 1. Validate input
if (!email || !password) {
throw new Error('Email and password required');
}
if (!this.isValidEmail(email)) {
throw new Error('Invalid email format');
}
if (password.length < 8) {
throw new Error('Password too short');
}
// 2. Find user
const user = await this.userRepo.findByEmail(email);
if (!user) {
throw new Error('User not found');
}
// 3. Check password
const isValid = await this.comparePassword(password, user.password);
if (!isValid) {
throw new Error('Invalid password');
}
// 4. Check if user is locked
if (user.lockedUntil && user.lockedUntil > new Date()) {
throw new Error('Account locked');
}
// 5. Generate tokens
const accessToken = this.generateAccessToken(user);
const refreshToken = this.generateRefreshToken(user);
// 6. Save refresh token
await this.tokenRepo.save(refreshToken);
// 7. Update last login
await this.userRepo.update(user.id, {
lastLoginAt: new Date()
});
// 8. Clear failed login attempts
await this.userRepo.clearFailedAttempts(user.id);
// 9. Return result
return {
user,
accessToken,
refreshToken
};
}
// ❌ CODE SMELL: Duplicate validation
isValidEmail(email: string) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// ❌ CODE SMELL: Duplicate validation in register
async register(data: RegisterData) {
if (!data.email || !data.password) {
throw new Error('Email and password required');
}
if (!this.isValidEmail(data.email)) {
throw new Error('Invalid email format');
}
if (data.password.length < 8) {
throw new Error('Password too short');
}
// ... more duplicate validation
}
}
// 2. Metrics
// - Cyclomatic complexity: 15 (should be < 10)
// - Lines per function: 50+ (should be < 20)
// - Responsibilities: 9 (should be 1)
// - Code duplication: 35% (should be < 5%)🔍 Code Analysis Complete
Module: AuthService
Location: src/services/auth.service.ts
Issues Found:
1. God Class
• 9 methods, too many responsibilities
• 350+ lines total
• Handles auth, tokens, validation, locking
2. Long Methods
• login(): 50 lines
• OAuthLogin(): 60 lines
• register(): 45 lines
3. Code Duplication (35%)
• Email validation duplicated in 4 places
• Password validation duplicated in 3 places
• Token generation duplicated in 2 places
4. Low Testability
• Tight coupling to repositories
• Hard to mock dependencies
• No dependency injection
5. Violates SRP (Single Responsibility Principle)
• Should separate:
- Authentication logic
- Token management
- Validation
- User locking
Refactoring Priority: HIGH
Estimated Time: 1-2 hoursStep 2: Refactoring Plan
Agent: @architect Time: 5-10 minutes
Plan creation:## Refactoring Plan: AuthService
### Phase 1: Extract Validation (15 minutes)
**Goal**: Create separate validation module
**Actions**:
1. Create `src/lib/validation.ts`
2. Extract email validation
3. Extract password validation
4. Add validation schemas
**Benefits**:
- Remove 35% of code duplication
- Reusable across codebase
- Easier to test
---
### Phase 2: Extract Token Management (20 minutes)
**Goal**: Create TokenService
**Actions**:
1. Create `src/services/token.service.ts`
2. Move token generation to TokenService
3. Move token validation to TokenService
4. Move token storage to TokenService
**Benefits**:
- Single responsibility for tokens
- Easier to swap token libraries
- Better testability
---
### Phase 3: Simplify AuthService (25 minutes)
**Goal**: Focus only on authentication flow
**Actions**:
1. Remove token logic (delegates to TokenService)
2. Remove validation logic (delegates to Validation)
3. Extract login steps into smaller functions
4. Add dependency injection
**Benefits**:
- AuthService focuses on auth flow
- Each method < 20 lines
- Complexity < 10
---
### Phase 4: Add Tests (20 minutes)
**Goal**: Comprehensive test coverage
**Actions**:
1. Unit tests for Validation
2. Unit tests for TokenService
3. Unit tests for AuthService
4. Integration tests for auth flow
**Benefits**:
- Confidence in refactored code
- Prevent regressions
---
### Success Metrics
**Before**:
- Cyclomatic complexity: 15
- Lines per function: 50+
- Code duplication: 35%
- Test coverage: 45%
**After** (target):
- Cyclomatic complexity: < 10
- Lines per function: < 20
- Code duplication: < 5%
- Test coverage: > 80%
### Risk Mitigation
1. **Incremental changes**: One phase at a time
2. **Test after each phase**: No behavior changes
3. **Commit after each phase**: Easy rollback
4. **Baseline tests**: Ensure no regressionsStep 3: Incremental Refactoring
Agent: @backend Time: 60-90 minutes (total)
Phase 1: Extract Validation
// BEFORE: Validation scattered everywhere
// src/services/auth.service.ts
export class AuthService {
isValidEmail(email: string) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
isValidPassword(password: string) {
return password.length >= 8;
}
async login(email: string, password: string) {
if (!this.isValidEmail(email)) {
throw new Error('Invalid email');
}
if (!this.isValidPassword(password)) {
throw new Error('Invalid password');
}
// ...
}
async register(data: RegisterData) {
if (!this.isValidEmail(data.email)) {
throw new Error('Invalid email');
}
if (!this.isValidPassword(data.password)) {
throw new Error('Invalid password');
}
// ...
}
}
// AFTER: Centralized validation
// src/lib/validation.ts
import { z } from 'zod';
// Reusable validation schemas
export const emailSchema = z.string().email('Invalid email format');
export const passwordSchema = z.string().min(8, 'Password must be at least 8 characters');
export const loginSchema = z.object({
email: emailSchema,
password: passwordSchema,
});
export const registerSchema = z.object({
email: emailSchema,
password: passwordSchema,
name: z.string().min(2, 'Name must be at least 2 characters'),
});
// Validation functions
export function validateEmail(email: string): boolean {
return emailSchema.safeParse(email).success;
}
export function validatePassword(password: string): boolean {
return passwordSchema.safeParse(password).success;
}
// Usage in AuthService
// src/services/auth.service.ts
import { loginSchema, registerSchema } from '@/lib/validation';
export class AuthService {
async login(input: LoginInput) {
// Validate using schema
const validated = loginSchema.parse(input);
// Proceed with validated data
const user = await this.userRepo.findByEmail(validated.email);
// ...
}
async register(input: RegisterInput) {
// Validate using schema
const validated = registerSchema.parse(input);
// Proceed with validated data
return this.userRepo.create(validated);
}
}npm test
✓ All tests passing
✓ No behavior changes
✓ Code duplication reduced from 35% to 15%
Commit: "refactor: extract validation to separate module"Phase 2: Extract Token Management
// BEFORE: Token logic mixed with auth
// src/services/auth.service.ts
export class AuthService {
async login(email: string, password: string) {
// ... auth logic ...
// Token generation mixed in
const accessToken = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, {
expiresIn: '15m'
});
const refreshToken = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, {
expiresIn: '7d'
});
await this.tokenRepo.save(refreshToken);
return { user, accessToken, refreshToken };
}
async verifyToken(token: string) {
return jwt.verify(token, process.env.JWT_SECRET);
}
}
// AFTER: Separate TokenService
// src/services/token.service.ts
export class TokenService {
constructor(private tokenRepo: TokenRepository) {}
generateAccessToken(user: User): string {
return jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET!,
{ expiresIn: '15m' }
);
}
generateRefreshToken(user: User): string {
return jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET!,
{ expiresIn: '7d' }
);
}
async createTokenPair(user: User): Promise<TokenPair> {
const accessToken = this.generateAccessToken(user);
const refreshToken = this.generateRefreshToken(user);
await this.tokenRepo.save(refreshToken);
return { accessToken, refreshToken };
}
verifyAccessToken(token: string): TokenPayload {
return jwt.verify(token, process.env.JWT_SECRET!) as TokenPayload;
}
verifyRefreshToken(token: string): TokenPayload {
return jwt.verify(token, process.env.JWT_SECRET!) as TokenPayload;
}
}
// Updated AuthService
// src/services/auth.service.ts
export class AuthService {
constructor(
private userRepo: UserRepository,
private tokenService: TokenService // ✅ Dependency injection
) {}
async login(email: string, password: string) {
// ... auth logic ...
// Delegate token creation to TokenService
const tokens = await this.tokenService.createTokenPair(user);
return { user, ...tokens };
}
async verifyToken(token: string) {
return this.tokenService.verifyAccessToken(token);
}
}npm test
✓ All tests passing
✓ No behavior changes
✓ Single responsibility: AuthService focuses on auth, TokenService on tokens
Commit: "refactor: extract token management to TokenService"Phase 3: Simplify AuthService
// BEFORE: Long, complex methods
// src/services/auth.service.ts
export class AuthService {
async login(email: string, password: string) {
// 50 lines of mixed concerns
}
}
// AFTER: Small, focused methods
// src/services/auth.service.ts
export class AuthService {
constructor(
private userRepo: UserRepository,
private tokenService: TokenService,
private lockoutService: LockoutService
) {}
async login(email: string, password: string): Promise<LoginResult> {
// Validate input
const validated = loginSchema.parse({ email, password });
// Find user (delegated)
const user = await this.findUserByEmail(validated.email);
// Check credentials (delegated)
await this.verifyCredentials(user, validated.password);
// Check lockout (delegated)
await this.lockoutService.checkLockout(user);
// Generate tokens (delegated)
const tokens = await this.tokenService.createTokenPair(user);
// Update last login (delegated)
await this.userRepo.updateLastLogin(user.id);
return { user, ...tokens };
}
// Small, focused helper methods
private async findUserByEmail(email: string): Promise<User> {
const user = await this.userRepo.findByEmail(email);
if (!user) {
throw new UnauthorizedError('Invalid credentials');
}
return user;
}
private async verifyCredentials(user: User, password: string): Promise<void> {
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
await this.lockoutService.recordFailedAttempt(user);
throw new UnauthorizedError('Invalid credentials');
}
}
}# Before refactoring
Cyclomatic complexity: 15
Lines per function: 50+
Test coverage: 45%
# After refactoring
Cyclomatic complexity: 5 ✅ (reduced 67%)
Lines per function: 12 ✅ (reduced 76%)
Test coverage: 65% ✅ (improved 20%)
Commit: "refactor: simplify AuthService with focused methods"Phase 4: Add Comprehensive Tests
// src/services/__tests__/auth.service.test.ts
describe('AuthService', () => {
let service: AuthService;
let mockUserRepo: UserRepository;
let mockTokenService: TokenService;
let mockLockoutService: LockoutService;
beforeEach(() => {
mockUserRepo = createMockUserRepository();
mockTokenService = createMockTokenService();
mockLockoutService = createMockLockoutService();
service = new AuthService(
mockUserRepo,
mockTokenService,
mockLockoutService
);
});
describe('login', () => {
it('should authenticate user with valid credentials', async () => {
// Arrange
const user = createTestUser();
mockUserRepo.findByEmail.mockResolvedValue(user);
mockTokenService.createTokenPair.mockResolvedValue({
accessToken: 'access-token',
refreshToken: 'refresh-token'
});
// Act
const result = await service.login('test@example.com', 'password123');
// Assert
expect(result.user).toEqual(user);
expect(result.accessToken).toBe('access-token');
expect(result.refreshToken).toBe('refresh-token');
});
it('should throw error for non-existent user', async () => {
// Arrange
mockUserRepo.findByEmail.mockResolvedValue(null);
// Act & Assert
await expect(
service.login('missing@example.com', 'password123')
).rejects.toThrow('Invalid credentials');
});
it('should throw error for invalid password', async () => {
// Arrange
const user = createTestUser();
mockUserRepo.findByEmail.mockResolvedValue(user);
mockLockoutService.recordFailedAttempt.mockResolvedValue(undefined);
// Act & Assert
await expect(
service.login('test@example.com', 'wrongpassword')
).rejects.toThrow('Invalid credentials');
expect(mockLockoutService.recordFailedAttempt).toHaveBeenCalledWith(user);
});
it('should check lockout before authenticating', async () => {
// Arrange
const user = createTestUser();
mockUserRepo.findByEmail.mockResolvedValue(user);
mockLockoutService.checkLockout.mockImplementation(() => {
throw new LockedError('Account locked');
});
// Act & Assert
await expect(
service.login('test@example.com', 'password123')
).rejects.toThrow('Account locked');
});
});
describe('register', () => {
it('should create new user with hashed password', async () => {
// Arrange
const data = {
email: 'new@example.com',
password: 'password123',
name: 'New User'
};
mockUserRepo.create.mockResolvedValue({
id: '123',
email: data.email,
name: data.name
});
// Act
const result = await service.register(data);
// Assert
expect(result).toHaveProperty('id');
expect(result.email).toBe(data.email);
expect(mockUserRepo.create).toHaveBeenCalled();
});
});
});
// src/services/__tests__/token.service.test.ts
describe('TokenService', () => {
it('should generate valid access token', () => {
const user = createTestUser();
const service = new TokenService(mockTokenRepo);
const token = service.generateAccessToken(user);
expect(token).toBeTruthy();
expect(typeof token).toBe('string');
});
it('should verify valid access token', () => {
const user = createTestUser();
const service = new TokenService(mockTokenRepo);
const token = service.generateAccessToken(user);
const payload = service.verifyAccessToken(token);
expect(payload.userId).toBe(user.id);
});
it('should throw error for invalid token', () => {
const service = new TokenService(mockTokenRepo);
expect(() => {
service.verifyAccessToken('invalid-token');
}).toThrow();
});
});npm test -- --coverage
Coverage Summary:
AuthService: 92% ✅ (was 45%)
TokenService: 89% ✅ (new)
Validation: 95% ✅ (new)
Overall coverage: 87% ✅ (target: 80%)
Commit: "test: add comprehensive tests for refactored auth"Step 4: Validation
Agent: @reviewer Time: 2-3 minutes
Validation checks:# 1. All tests pass
npm test
✓ 65 tests passed (was 25)
# 2. No behavior changes
# Run E2E tests
npm run test:e2e
✓ All user flows work correctly
# 3. Quality gates
/agentful-validate
Quality Gates:
✅ TypeScript: No errors
✅ Lint: No errors
✅ Dead Code: None
✅ Tests: All passing
✅ Coverage: 87% (target 80%)
✅ Security: No issues
# 4. Performance comparison
# Before: 850ms for login flow
# After: 720ms for login flow
# ✅ 15% performance improvement
# 5. Code metrics comparison
# Cyclomatic complexity: 15 → 5 ✅
# Lines per function: 50 → 12 ✅
# Code duplication: 35% → 3% ✅✅ Refactoring Complete: AuthService
Time: 1 hour 45 minutes
Commits: 4 (one per phase)
Improvements:
• Cyclomatic complexity: 15 → 5 (67% reduction)
• Lines per function: 50 → 12 (76% reduction)
• Code duplication: 35% → 3% (91% reduction)
• Test coverage: 45% → 87% (93% increase)
• Performance: 15% faster login flow
Quality Gates:
✅ All tests passing (65/65)
✅ No behavior changes
✅ No regressions detected
✅ Documentation updated
Modules Created:
✓ src/lib/validation.ts
✓ src/services/token.service.ts
✓ src/services/lockout.service.ts
Modules Refactored:
✓ src/services/auth.service.ts (simplified)
Tests Added:
✓ 40 new tests
✓ All refactored modules covered
Ready to ship!Real Example: Refactoring Todo List
Before: Tightly Coupled
// src/components/TodoList.tsx
export function TodoList() {
const [todos, setTodos] = useState<Todo[]>([]);
const [filter, setFilter] = useState<'all' | 'active' | 'completed'>('all');
// ❌ Mixed concerns: fetching, filtering, state management
useEffect(() => {
fetch('/api/todos')
.then(res => res.json())
.then(data => setTodos(data));
}, []);
// ❌ Complex filtering logic inside component
const filteredTodos = useMemo(() => {
switch (filter) {
case 'active':
return todos.filter(t => !t.completed);
case 'completed':
return todos.filter(t => t.completed);
default:
return todos;
}
}, [todos, filter]);
// ❌ Business logic in component
const addTodo = async (text: string) => {
const newTodo = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, completed: false })
}).then(res => res.json());
setTodos([...todos, newTodo]);
};
const toggleTodo = async (id: string) => {
const todo = todos.find(t => t.id === id);
const updated = await fetch(`/api/todos/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ completed: !todo.completed })
}).then(res => res.json());
setTodos(todos.map(t => t.id === id ? updated : t));
};
const deleteTodo = async (id: string) => {
await fetch(`/api/todos/${id}`, { method: 'DELETE' });
setTodos(todos.filter(t => t.id !== id));
};
// ❌ 120+ lines in single component
return (
<div>
{/* UI code mixed with business logic */}
</div>
);
}- 120+ lines
- Mixed concerns (data fetching, business logic, UI)
- Hard to test
- Duplicated API calls
- No separation of concerns
After: Separated Concerns
// 1. Custom hook for data management
// src/hooks/useTodos.ts
export function useTodos() {
const [todos, setTodos] = useState<Todo[]>([]);
const [filter, setFilter] = useState<Filter>('all');
// ✅ Data fetching separated
useEffect(() => {
todoApi.getAll().then(setTodos);
}, []);
// ✅ API calls delegated to service
const addTodo = async (text: string) => {
const newTodo = await todoApi.create({ text, completed: false });
setTodos(prev => [...prev, newTodo]);
};
const toggleTodo = async (id: string) => {
const todo = todos.find(t => t.id === id);
const updated = await todoApi.update(id, { completed: !todo.completed });
setTodos(prev => prev.map(t => t.id === id ? updated : t));
};
const deleteTodo = async (id: string) => {
await todoApi.delete(id);
setTodos(prev => prev.filter(t => t.id !== id));
};
// ✅ Filtering logic in hook
const filteredTodos = useMemo(() => {
return filterTodos(todos, filter);
}, [todos, filter]);
return {
todos: filteredTodos,
filter,
setFilter,
addTodo,
toggleTodo,
deleteTodo
};
}
// 2. API service
// src/lib/todoApi.ts
export const todoApi = {
getAll: () => fetch('/api/todos').then(res => res.json()),
create: (data: CreateTodoDto) => fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}).then(res => res.json()),
update: (id: string, data: UpdateTodoDto) => fetch(`/api/todos/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}).then(res => res.json()),
delete: (id: string) => fetch(`/api/todos/${id}`, { method: 'DELETE' }),
};
// 3. Utility functions
// src/lib/todoUtils.ts
export function filterTodos(todos: Todo[], filter: Filter): Todo[] {
switch (filter) {
case 'active':
return todos.filter(t => !t.completed);
case 'completed':
return todos.filter(t => t.completed);
default:
return todos;
}
}
// 4. Simplified component (only UI)
// src/components/TodoList.tsx
export function TodoList() {
// ✅ All business logic in hook
const { todos, filter, setFilter, addTodo, toggleTodo, deleteTodo } = useTodos();
// ✅ Component only handles UI
return (
<div>
<TodoFilter current={filter} onChange={setFilter} />
<TodoInput onSubmit={addTodo} />
<TodoListItems
todos={todos}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
</div>
);
}- Component: 120 lines → 15 lines (87% reduction)
- Separation of concerns
- Easy to test
- Reusable logic
- Clear responsibilities
Refactoring Patterns
1. Extract Method
When: Method is too long or does multiple things
Before:function processOrder(order: Order) {
// 50 lines of validation, processing, saving
}function processOrder(order: Order) {
validateOrder(order);
const processed = calculateOrder(order);
saveOrder(processed);
}2. Extract Class
When: Class has too many responsibilities
Before:class UserService {
async create() { /* ... */ }
async authenticate() { /* ... */ }
async sendEmail() { /* ... */ }
async generateReport() { /* ... */ }
}class UserService {
async create() { /* ... */ }
async authenticate() { /* ... */ }
}
class EmailService {
async sendEmail() { /* ... */ }
}
class ReportService {
async generateReport() { /* ... */ }
}3. Replace Conditional with Polymorphism
When: Complex switch/if-else chains
Before:function calculateDiscount(order: Order) {
if (order.type === 'regular') {
return order.total * 0.05;
} else if (order.type === 'premium') {
return order.total * 0.10;
} else if (order.type === 'vip') {
return order.total * 0.15;
}
}interface DiscountStrategy {
calculate(order: Order): number;
}
class RegularDiscount implements DiscountStrategy {
calculate(order: Order) { return order.total * 0.05; }
}
class PremiumDiscount implements DiscountStrategy {
calculate(order: Order) { return order.total * 0.10; }
}
class VipDiscount implements DiscountStrategy {
calculate(order: Order) { return order.total * 0.15; }
}4. Extract Module
When: Related functionality scattered across files
Before:// Auth duplicated across multiple files
// auth.service.ts, user.service.ts, session.service.ts// src/features/auth/
// - services/
// - components/
// - hooks/
// - types/Best Practices
1. Refactor in Small Steps
# Good: One small change at a time
1. Extract validation
2. Run tests
3. Commit
4. Extract tokens
5. Run tests
6. Commit
# Bad: Big bang refactor
1. Rewrite entire module
2. Tests fail everywhere
3. Can't debug
4. Hard to rollback2. Test Continuously
# After every change
npm test
# If tests fail:
# 1. Fix tests
# 2. Or revert change
# 3. Try different approach
# Never proceed with failing tests3. Preserve Behavior
# Refactoring ≠ Adding features
Before: User can login
After: User can login (same behavior)
Only implementation changes,
external behavior stays the same4. Document Changes
# Good commit messages
refactor: extract validation to separate module
- Created src/lib/validation.ts
- Moved email/password validation
- Replaced with reusable schemas
- Reduced code duplication by 35%
No behavior changes. All tests passing.5. Measure Improvement
# Before refactoring
- Code complexity: 15
- Test coverage: 45%
- Duplication: 35%
# After refactoring
- Code complexity: 5 ✅
- Test coverage: 87% ✅
- Duplication: 3% ✅
# Verify improvements before considering completeNext Steps
Feature Development
After refactoring, build new features confidently → Feature Development Guide
Testing Workflow
Improve test coverage with testing workflow → Testing Guide
Quick Reference
Start Refactoring:/agentful-start
# Specify refactoring goal in prompt/agentful-validate
# Ensure no regressions# Before
npm test -- --coverage > baseline.json
# After
npm test -- --coverage > current.json
diff baseline.json current.json