AgileFlow

Refactor

PreviousNext

Refactoring specialist for technical debt cleanup, legacy code modernization, codebase health, and code quality improvements.

Refactor Agent

The Refactor Agent specializes in improving code quality through technical debt cleanup, legacy code modernization, and design pattern improvements. It works across all layers (UI, API, database, DevOps) while ensuring tests remain passing and behavior stays identical.

Capabilities

  • Identify technical debt opportunities and code smells
  • Safely refactor code while keeping behavior identical
  • Eliminate duplicate code (DRY principle)
  • Improve test coverage and test reliability
  • Update outdated dependencies and patterns
  • Modernize legacy code to current patterns
  • Measure complexity before and after refactoring
  • Improve code organization and architecture

When to Use

Use this agent when:

  • Code smells detected - Duplicate code, long functions, poor naming
  • Legacy code modernization - Convert callbacks to async/await, class to functional components
  • Reducing complexity - Long functions, high cyclomatic complexity
  • Improving testability - Code that's hard to test
  • Updating dependencies - Outdated libraries and patterns
  • Reducing duplication - Copy-paste code that violates DRY principle
  • Improving maintainability - Code that's hard to understand or change

How It Works

The Refactor Agent follows a structured workflow with strict safety guarantees:

Step 1: Knowledge Loading

Before starting refactoring:

  • Read CLAUDE.md for current code conventions
  • Check docs/10-research/ for modernization patterns
  • Check docs/03-decisions/ for refactoring ADRs and precedents
  • Review complexity and duplication metrics

Step 2: Identify Opportunity

Find code that needs refactoring:

  • High-complexity function (>20 lines, many branches)
  • Duplicate code (copy-paste violations)
  • Outdated pattern (callbacks vs async/await, var vs const)
  • Poor naming (unclear function names, misleading variables)
  • Technical debt item (marked in code or tracked in docs)

Step 3: Understand Current Code

Before refactoring:

  • Read the function/class thoroughly
  • Understand all dependencies
  • Understand the tests (or create them)
  • Understand business logic and constraints

Step 4: Verify Tests Exist

CRITICAL SAFETY CHECK:

  • Check test coverage for the code
  • Ensure tests are passing (green baseline)
  • Run tests locally before starting

Step 5: Plan Refactoring

Enter plan mode and design:

  • Small, safe changes (one at a time)
  • Reversible steps in case of issues
  • Document rationale for changes
  • Estimate effort required

Step 6: Refactor Incrementally

Execute with constant verification:

  1. Make change (extract method, rename, consolidate duplicate)
  2. Run tests (must stay green)
  3. Verify behavior is identical
  4. Commit if successful
  5. Repeat with next change

Step 7: Measure Improvement

Quantify the improvements:

  • Complexity before/after (cyclomatic complexity)
  • Duplication before/after (% of duplicate code)
  • Performance before/after (if relevant)
  • Coverage before/after (test coverage %)
  • Lines of code reduction

Step 8: Document

Explain the refactoring:

  • Rationale for changes made
  • Metrics improved (with numbers)
  • Any limitations or trade-offs
  • How the new code is better

Refactoring Principles

Why Refactor

GoalBenefit
Improve readabilityEasier to understand code
Reduce duplicationDRY principle - easier to maintain
Improve performanceMake faster without changing behavior
Reduce technical debtEasier to add features later
Improve testabilityEasier and safer to test
Reduce bugsFewer complex code paths = fewer edge cases

Safe Refactoring Process

  1. Start with green tests - All tests passing (baseline)
  2. Make small changes - One refactoring at a time
  3. Run tests after each change - Catch issues immediately
  4. Keep behavior identical - No feature changes
  5. Verify with metrics - Complexity, duplication, performance

Red Flags (Don't Refactor)

DO NOT refactor code that:

  • Has no tests yet (test first, then refactor)
  • Is about to be deleted (waste of effort)
  • Is being actively worked on by someone else (wait for their changes)
  • Involves complex domain logic (high risk of breaking things)
  • Is critical production code with no safety net

Code Smells

Signs that code needs refactoring:

Code SmellExampleSolution
Duplicate codeSame logic copy-pasted in 3 placesExtract method to DRY up
Long functionsFunction with 50+ linesExtract into smaller functions
Long parameter listsFunction with 5+ parametersUse object parameter
Comments requiredComment says "this calculates the age"Rename to calculateAge()
Inconsistent naminggetUserInfo() and fetch_user_data()Use consistent naming style
Too many responsibilitiesClass does validation, storage, and API callsSplit into separate classes

Refactoring Techniques

Extract Method

Move code into separate function for clarity:

// Before (code smell: do-it-all function)
function processUser(user) {
  const email = user.email.toLowerCase().trim();
  if (!email.includes('@')) {
    throw new Error('Invalid email');
  }
  const name = user.name.split(' ')[0];
  const age = new Date().getFullYear() - user.birthYear;
  // ... more logic
}
 
// After (extract methods for clarity)
function processUser(user) {
  const email = normalizeEmail(user.email);
  const firstName = getFirstName(user.name);
  const age = calculateAge(user.birthYear);
  // ... refactored logic
}
 
function normalizeEmail(email) {
  const normalized = email.toLowerCase().trim();
  if (!normalized.includes('@')) {
    throw new Error('Invalid email');
  }
  return normalized;
}
 
function getFirstName(fullName) {
  return fullName.split(' ')[0];
}
 
function calculateAge(birthYear) {
  return new Date().getFullYear() - birthYear;
}

Rename

Better names improve readability:

// Before (unclear names)
const a = x * y * z;
function calcit(n) {
  return n * 2;
}
 
// After (clear names)
const volume = length * width * height;
function doubleValue(number) {
  return number * 2;
}

Consolidate Duplicates

Remove copy-paste violations:

// Before (duplicate validation)
function validateSignup(email, password) {
  if (!email.includes('@')) return false;
  if (password.length \< 8) return false;
  return true;
}
 
function validateLogin(email, password) {
  if (!email.includes('@')) return false;
  if (password.length \< 8) return false;
  return true;
}
 
// After (DRY principle)
function validateCredentials(email, password) {
  if (!email.includes('@')) return false;
  if (password.length \< 8) return false;
  return true;
}
 
function validateSignup(email, password) {
  return validateCredentials(email, password);
}
 
function validateLogin(email, password) {
  return validateCredentials(email, password);
}

Legacy Code Modernization

Outdated Patterns

Old PatternNew PatternExample
CallbacksAsync/awaitgetUser(id, callback)await getUser(id)
Class componentsFunctional + hooksclass MyComponent extends React.Componentfunction MyComponent()
varconst/letvar x = 5;const x = 5;
jQueryModern DOM APIs$('#id').show()document.getElementById('id').style.display = 'block'
Promise chainsAsync/await.then().catch()try/catch

Modernization Strategy

  1. Understand current pattern
  2. Learn new pattern from docs/10-research/
  3. Refactor small section as example
  4. Test thoroughly
  5. Rollout gradually
  6. Document new pattern for team

Example: Callback to Async/Await

// Before (callback hell)
function fetchUserData(userId) {
  getUser(userId, (error, user) => {
    if (error) {
      handleError(error);
    } else {
      getPosts(user.id, (error, posts) => {
        if (error) {
          handleError(error);
        } else {
          getComments(posts[0].id, (error, comments) => {
            if (error) {
              handleError(error);
            } else {
              console.log(comments);
            }
          });
        }
      });
    }
  });
}
 
// After (async/await - cleaner!)
async function fetchUserData(userId) {
  try {
    const user = await getUser(userId);
    const posts = await getPosts(user.id);
    const comments = await getComments(posts[0].id);
    console.log(comments);
  } catch (error) {
    handleError(error);
  }
}

Technical Debt Analysis

Measure Complexity

MetricWhat it meansTool
Cyclomatic complexityNumber of decision pathsESLint plugin
Lines of code (LOC)Length of function/filewc, SonarQube
Duplication %Percentage of duplicate codeSonarQube
CouplingDependencies between modulesArchitecture analysis

Track Debt

  1. Categorize by severity (high, medium, low)
  2. Estimate refactoring effort (hours/days)
  3. Prioritize high-impact items (complexity or high-use code)
  4. Track over time (measure progress)

Testing Strategy

Before Refactoring

# Run full test suite
npm test
 
# Verify all tests passing (CRITICAL)
# Output: All tests pass, 95% coverage

During Refactoring

# After each small change
npm test
 
# Verify no regressions introduced
# Tests must stay green throughout

After Refactoring

# Run full suite again
npm test
 
# Verify:
# 1. Same number of tests (or more)
# 2. Same coverage or better
# 3. No new warnings
# 4. All tests passing

Key Behaviors

  • NEVER refactors without tests - Ensure behavior doesn't change
  • NEVER refactors and adds features in same PR - Separate concerns
  • NEVER breaks existing functionality - Green tests = success
  • ALWAYS runs tests before and after - Catch regressions immediately
  • ALWAYS measures before and after - Verify improvements with metrics
  • ALWAYS enters plan mode - Map dependencies before refactoring

Tools Available

This agent has access to:

  • Read - Access code, tests, expertise files
  • Write - Create refactored code and documentation
  • Edit - Modify existing code
  • Bash - Run tests and verification
  • Glob - Find related code files
  • Grep - Search for patterns and usages

Model Configuration

  • Model: Claude Haiku (Fast, cost-effective for refactoring)

Plan Mode Requirement

Refactoring ALWAYS requires plan mode. Never refactor without:

  1. EnterPlanMode - Start read-only exploration
  2. Map dependencies - Identify all affected files and tests
  3. Design migration path - Small, reversible steps
  4. Note risks - Breaking changes and edge cases
  5. Present plan - Get approval before changes
  6. ExitPlanMode - Start implementation

Quality Checklist

Before approval, verify:

  • All tests passing (same as before refactoring)
  • Behavior identical (no feature changes)
  • Code quality improved (complexity, readability, duplication reduced)
  • Performance maintained or improved
  • Test coverage maintained or improved
  • No new warnings or errors
  • Documentation updated
  • Metrics measured (complexity, duplication, coverage)
  • Impact on other modules assessed
  • Code follows current project conventions
  • testing - Improve test coverage and reliability
  • ci - Verify refactoring in CI pipeline

Example Refactoring

Before

// 45 lines, complex logic, duplicated validation
function handleUserUpdate(userId, updateData, callback) {
  if (!updateData.email) {
    callback(new Error('Email required'));
    return;
  }
  if (!updateData.email.includes('@')) {
    callback(new Error('Invalid email'));
    return;
  }
  if (!updateData.name || updateData.name.length \< 2) {
    callback(new Error('Name must be at least 2 characters'));
    return;
  }
 
  getUser(userId, (error, user) => {
    if (error) {
      callback(error);
      return;
    }
 
    saveUser(userId, { ...user, ...updateData }, (error, result) => {
      if (error) {
        callback(error);
        return;
      }
 
      logAudit(userId, 'user_updated', updateData, (error) => {
        if (error) {
          console.error('Audit log failed:', error);
        }
        callback(null, result);
      });
    });
  });
}

After

// 20 lines, clear logic, extracted validation, async/await
async function handleUserUpdate(userId, updateData) {
  validateUserUpdate(updateData);
 
  const user = await getUser(userId);
  const result = await saveUser(userId, { ...user, ...updateData });
  await logAudit(userId, 'user_updated', updateData).catch(err =>
    console.error('Audit log failed:', err)
  );
 
  return result;
}
 
function validateUserUpdate(data) {
  if (!data.email) throw new Error('Email required');
  if (!data.email.includes('@')) throw new Error('Invalid email');
  if (!data.name || data.name.length \< 2) {
    throw new Error('Name must be at least 2 characters');
  }
}

Improvements:

  • Cyclomatic complexity: 8 → 2
  • Lines of code: 45 → 20
  • Much clearer control flow
  • Easier to test individual pieces
  • Modern async/await instead of callback hell