BMAD-METHOD/expansion-packs/bmad-tdd-methodology/tasks/tdd-refactor.md

8.7 KiB

tdd-refactor

Safely refactor code while keeping all tests green - the "Refactor" phase of TDD.

Purpose

Improve code quality, eliminate duplication, and enhance design while maintaining all existing functionality. This is the "Refactor" phase of TDD where we make the code clean and maintainable.

Prerequisites

  • All tests are passing (tdd.status: green)
  • Implementation is complete and functional
  • Test suite provides safety net for refactoring
  • Code follows basic project standards

Inputs

required:
  - story_id: '{epic}.{story}' # e.g., "1.3"
  - story_path: '{devStoryLocation}/{epic}.{story}.*.md' # Path from core-config.yaml
  - passing_tests: # All tests should be green
      - id: test identifier
      - status: passing
  - implementation_files: # Source files to potentially refactor
      - path: file path
      - purpose: what it does

Process

1. Identify Refactoring Opportunities

Code Smells to Look For:

common_smells:
  duplication:
    - Repeated code blocks
    - Similar logic in different places
    - Copy-paste patterns

  complexity:
    - Long methods/functions (>10-15 lines)
    - Too many parameters (>3-4)
    - Nested conditions (>2-3 levels)
    - Complex boolean expressions

  naming:
    - Unclear variable names
    - Non-descriptive function names
    - Inconsistent naming conventions

  structure:
    - God objects/classes doing too much
    - Primitive obsession
    - Feature envy (method using more from other class)
    - Long parameter lists

2. Plan Refactoring Steps

Refactoring Strategy:

  • One change at a time: Make small, atomic improvements
  • Run tests after each change: Ensure no functionality breaks
  • Commit frequently: Create checkpoints for easy rollback
  • Improve design: Move toward better architecture

Common Refactoring Techniques:

extract_methods:
  when: 'Function is too long or doing multiple things'
  technique: 'Extract complex logic into named methods'

rename_variables:
  when: "Names don't clearly express intent"
  technique: 'Use intention-revealing names'

eliminate_duplication:
  when: 'Same code appears in multiple places'
  technique: 'Extract to shared function/method'

simplify_conditionals:
  when: 'Complex boolean logic is hard to understand'
  technique: 'Extract to well-named boolean methods'

introduce_constants:
  when: 'Magic numbers or strings appear repeatedly'
  technique: 'Create named constants'

3. Execute Refactoring

Step-by-Step Process:

  1. Choose smallest improvement
  2. Make the change
  3. Run all tests
  4. Commit if green
  5. Repeat

Example Refactoring Sequence:

// Before refactoring
function createUser(data) {
  if (!data.email.includes('@') || data.email.length < 5) {
    throw new Error('Invalid email format');
  }
  if (!data.name || data.name.trim().length === 0) {
    throw new Error('Name is required');
  }
  return {
    id: Math.floor(Math.random() * 1000000),
    ...data,
    createdAt: new Date().toISOString(),
  };
}

// After refactoring - Step 1: Extract validation
function validateEmail(email) {
  return email.includes('@') && email.length >= 5;
}

function validateName(name) {
  return name && name.trim().length > 0;
}

function createUser(data) {
  if (!validateEmail(data.email)) {
    throw new Error('Invalid email format');
  }
  if (!validateName(data.name)) {
    throw new Error('Name is required');
  }
  return {
    id: Math.floor(Math.random() * 1000000),
    ...data,
    createdAt: new Date().toISOString(),
  };
}

// After refactoring - Step 2: Extract ID generation
function generateUserId() {
  return Math.floor(Math.random() * 1000000);
}

function createUser(data) {
  if (!validateEmail(data.email)) {
    throw new Error('Invalid email format');
  }
  if (!validateName(data.name)) {
    throw new Error('Name is required');
  }
  return {
    id: generateUserId(),
    ...data,
    createdAt: new Date().toISOString(),
  };
}

4. Test After Each Change

Critical Rule: Never proceed without green tests

# Run tests after each refactoring step
npm test
pytest
go test ./...

# If tests fail:
# 1. Undo the change
# 2. Understand what broke
# 3. Try smaller refactoring
# 4. Fix tests if they need updating (rare)

5. Collaborate with QA Agent

When to involve QA:

  • Tests need updating due to interface changes
  • New test cases identified during refactoring
  • Questions about test coverage adequacy
  • Validation of refactoring safety

6. Update Story Documentation

Track refactoring progress:

tdd:
  status: refactor # or done if complete
  cycle: 1
  refactoring_notes:
    - extracted_methods: ['validateEmail', 'validateName', 'generateUserId']
    - eliminated_duplication: 'Email validation logic'
    - improved_readability: 'Function names now express intent'

Output Requirements

1. Improved Code Quality

Measurable Improvements:

  • Reduced code duplication
  • Clearer naming and structure
  • Smaller, focused functions
  • Better separation of concerns

2. Maintained Test Coverage

# All tests still passing
✅ UserService > should create user with valid email
✅ UserService > should reject user with invalid email
✅ UserService > should require valid name

3 passing, 0 failing

3. Story File Updates

Append to TDD section:

## TDD Progress

### Refactor Phase - Cycle 1

**Date:** {current_date}
**Agents:** James (Dev) & Quinn (QA)

**Refactoring Completed:**

- ✅ Extracted validation functions for better readability
- ✅ Eliminated duplicate email validation logic
- ✅ Introduced generateUserId() for testability
- ✅ Simplified createUser() main logic

**Code Quality Improvements:**

- Function length reduced from 12 to 6 lines
- Three reusable validation functions created
- Magic numbers eliminated
- Test coverage maintained at 100%

**Files Modified:**

- src/services/user-service.js (refactored)

**All Tests Passing:****Next Step:** Story ready for review or next TDD cycle

Refactoring Guidelines

Safe Refactoring Practices

Always Safe:

  • Rename variables/functions
  • Extract methods
  • Inline temporary variables
  • Replace magic numbers with constants

Potentially Risky:

  • Changing method signatures
  • Modifying class hierarchies
  • Altering error handling
  • Changing async/sync behavior

Never Do During Refactor:

  • Add new features
  • Change external behavior
  • Remove existing functionality
  • Skip running tests

Code Quality Metrics

Before/After Comparison:

metrics_to_track:
  cyclomatic_complexity: 'Lower is better'
  function_length: 'Shorter is generally better'
  duplication_percentage: 'Should decrease'
  test_coverage: 'Should maintain 100%'

acceptable_ranges:
  function_length: '5-15 lines for most functions'
  parameters: '0-4 parameters per function'
  nesting_depth: 'Maximum 3 levels'

Advanced Refactoring Techniques

Design Pattern Introduction

When appropriate:

  • Template Method for algorithmic variations
  • Strategy Pattern for behavior selection
  • Factory Pattern for object creation
  • Observer Pattern for event handling

Caution: Only introduce patterns if they simplify the code

Architecture Improvements

layering:
  - Separate business logic from presentation
  - Extract data access concerns
  - Isolate external dependencies

dependency_injection:
  - Make dependencies explicit
  - Enable easier testing
  - Improve modularity

error_handling:
  - Consistent error types
  - Meaningful error messages
  - Proper error propagation

Error Handling

If tests fail during refactoring:

  1. Undo immediately - Use git to revert
  2. Analyze the failure - What assumption was wrong?
  3. Try smaller steps - More atomic refactoring
  4. Consider test updates - Only if interface must change

If code becomes more complex:

  • Refactoring went wrong direction
  • Revert and try different approach
  • Consider if change is actually needed

Completion Criteria

  • All identified code smells addressed or documented
  • All tests remain green throughout process
  • Code is more readable and maintainable
  • No new functionality added during refactoring
  • Story TDD status updated appropriately
  • Refactoring changes committed with clear messages
  • Code quality metrics improved or maintained
  • Ready for story completion or next TDD cycle

Key Principles

  • Green Bar: Never proceed with failing tests
  • Small Steps: Make incremental improvements
  • Behavior Preservation: External behavior must remain identical
  • Frequent Commits: Create rollback points
  • Test First: Let tests guide refactoring safety
  • Collaborative: Work with QA when test updates needed