Refactoring Legacy Code

Practical Exercise 6

Hands-On

Use AI to safely refactor and modernize existing code

Practical Objectives

1

Write characterization tests before refactoring

2

Extract methods and reduce complexity

3

Apply design patterns with AI guidance

4

Maintain functionality while improving code

Task 1: Analyze Legacy Code

This is legacy code that needs refactoring:

def process_order(data):
    result = {}
    if data:
        if 'items' in data:
            if len(data['items']) > 0:
                t = 0
                for i in data['items']:
                    if 'price' in i and 'qty' in i:
                        if i['qty'] > 0:
                            if i['price'] > 0:
                                t = t + (i['price'] * i['qty'])
                                if i.get('taxable', True):
                                    t = t + (i['price'] * i['qty'] * 0.08)
                result['subtotal'] = t
                if 'coupon' in data:
                    if data['coupon'] == 'SAVE10':
                        t = t * 0.9
                    if data['coupon'] == 'SAVE20':
                        t = t * 0.8
                result['total'] = t
                result['status'] = 'ok'
            else:
                result['status'] = 'error'
                result['msg'] = 'no items'
        else:
            result['status'] = 'error'
            result['msg'] = 'invalid'
    else:
        result['status'] = 'error'
        result['msg'] = 'no data'
    return result

Task 2: Write Characterization Tests

Before refactoring, capture current behavior:

Test Generation PromptGenerate characterization tests for this legacy function: [paste process_order code] I need tests that capture the CURRENT behavior, not ideal behavior. Include tests for: 1. Valid order with items (taxable and non-taxable) 2. Order with SAVE10 coupon 3. Order with SAVE20 coupon 4. Empty items list 5. Missing 'items' key 6. None/null input 7. Items with zero quantity or price These tests should PASS with the current code - they document existing behavior.

Task 3: Create Refactoring Plan

Refactoring Strategy PromptCreate a step-by-step refactoring plan for this code: [paste process_order code] Goals: - Reduce nesting (currently 7 levels deep) - Extract meaningful functions - Improve naming - Add type hints - Make it testable Constraints: - Keep the same input/output contract - Don't change behavior (tests must stay green) - One small change at a time - Each step should be a commit point Provide the plan as numbered steps with expected outcome.

Task 4: Extract Methods

Identify Extractions

  • validate_order_data()
  • calculate_item_total()
  • calculate_tax()
  • apply_coupon()
  • build_success_result()
  • build_error_result()

Refactoring Prompt

"Extract the tax calculation logic into a separate function called calculate_tax. Keep tests passing."

Rule: After EACH extraction, run tests. Only proceed if green.

Task 5: Replace Nesting with Guard Clauses

Transform deep nesting to early returns:

# Before (nested)
def process(data):
    if data:
        if 'items' in data:
            if len(data['items']) > 0:
                # do work
            else:
                return error
        else:
            return error
    else:
        return error
# After (guard clauses)
def process(data):
    if not data:
        return error
    if 'items' not in data:
        return error
    if len(data['items']) == 0:
        return error

    # do work (flat)
Guard Clause PromptRefactor this function to use guard clauses instead of nested ifs. Replace nested conditions with early returns. Keep the same behavior - tests must stay green.

Task 6: Apply Strategy Pattern

Refactor coupon logic using Strategy pattern:

from abc import ABC, abstractmethod

class DiscountStrategy(ABC):
    @abstractmethod
    def apply(self, total: float) -> float:
        pass

class NoDiscount(DiscountStrategy):
    def apply(self, total: float) -> float:
        return total

class PercentDiscount(DiscountStrategy):
    def __init__(self, percent: int):
        self.percent = percent

    def apply(self, total: float) -> float:
        return total * (1 - self.percent / 100)

# Usage
DISCOUNTS = {
    'SAVE10': PercentDiscount(10),
    'SAVE20': PercentDiscount(20),
}

def apply_discount(total: float, coupon: str) -> float:
    strategy = DISCOUNTS.get(coupon, NoDiscount())
    return strategy.apply(total)

Task 7: Complete Refactoring

Your refactored code should look like:

from dataclasses import dataclass
from typing import List, Optional

@dataclass
class OrderItem:
    price: float
    quantity: int
    taxable: bool = True

def process_order(data: Optional[dict]) -> dict:
    error = validate_order_data(data)
    if error:
        return build_error_result(error)

    items = [OrderItem(**item) for item in data['items']]
    subtotal = calculate_subtotal(items)
    total = apply_discount(subtotal, data.get('coupon'))

    return build_success_result(subtotal, total)

def validate_order_data(data: Optional[dict]) -> Optional[str]:
    if not data:
        return 'no data'
    if 'items' not in data:
        return 'invalid'
    if not data['items']:
        return 'no items'
    return None

Deliverables Checklist

Tests

  • Characterization tests written
  • All tests pass before refactoring
  • All tests pass after refactoring

Refactoring

  • Extracted 4+ methods
  • Reduced nesting to max 2 levels
  • Applied guard clauses

Design

  • Strategy pattern for discounts
  • Type hints added
  • Dataclasses for structure

Process

  • Git commits for each step
  • Tests run after each change
  • No behavior changes

Refactoring Complete!

You've safely modernized legacy code

Next: Practical 7 - API Integration with AI

Slide Overview