Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

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 ScopeTimeIterations
Function-level (single function)5-15 minutes2-5
Module-level (related components)15-45 minutes5-10
Feature-level (entire feature)45 min - 2 hours10-20
Codebase-level (cross-cutting concerns)2-6 hours20-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 passing

2. 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 changes

3. 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%)
Output:
🔍 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 hours

Step 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 regressions

Step 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);
  }
}
Run tests after phase 1:
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);
  }
}
Run tests after phase 2:
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');
    }
  }
}
Metrics after phase 3:
# 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();
  });
});
Final metrics:
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% ✅
Output:
✅ 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>
  );
}
Issues:
  • 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>
  );
}
Improvements:
  • 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
}
After:
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() { /* ... */ }
}
After:
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;
  }
}
After:
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
After:
// 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 rollback

2. 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 tests

3. Preserve Behavior

# Refactoring ≠ Adding features
 
Before: User can login
After: User can login (same behavior)
 
Only implementation changes,
external behavior stays the same

4. 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 complete

Next 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
Validate Refactoring:
/agentful-validate
# Ensure no regressions
Compare Metrics:
# Before
npm test -- --coverage > baseline.json
 
# After
npm test -- --coverage > current.json
diff baseline.json current.json