Foundations

Refactoring and Code Transformation

Lesson 4 of 4 Estimated Time 40 min

Refactoring and Code Transformation

Refactoring is one of the best uses for AI assistance. You have working code that needs improvement, but you don’t know exactly how to restructure it. The AI can see multiple approaches, understand patterns, and guide the transformation.

Types of Refactoring

Type 1: Function Extraction

Large functions doing too much:

Original:

def process_order(order_id):
    # Fetch order
    order = db.query(Order).get(order_id)

    # Validate
    if not order:
        raise NotFound()

    # Calculate tax
    tax = order.total * 0.08

    # Apply discount
    if order.customer.is_vip:
        discount = order.total * 0.1
    else:
        discount = order.total * 0.05

    # Update database
    order.tax = tax
    order.discount = discount
    db.session.commit()

    # Send email
    send_email(order.customer.email, ...)

    return order

Ask the AI:

"This function does too much. Extract it into smaller functions.
What would be good boundaries for refactoring?

Current code: [paste above]

Our error handling pattern: [show example]
Our email sending pattern: [show example]"

The AI might suggest:

def process_order(order_id):
    order = fetch_order(order_id)
    apply_pricing(order)
    persist_order(order)
    notify_customer(order)
    return order

Now each function has one responsibility.

Type 2: Rename for Clarity

Variables with unclear names:

Original:

const processData = (d) => {
  return d.map(x => ({...x, v: x.a * x.p}));
};

Ask:

"Make this more readable by renaming variables.
The function processes invoice items, where 'a' is amount
and 'p' is percentage rate."

Result:

const calculateInvoiceItemSubtotals = (items) => {
  return items.map(item => ({
    ...item,
    subtotal: item.amount * item.percentageRate
  }));
};

Type 3: Pattern Modernization

Old patterns to new patterns:

Original (callback hell):

function fetchUserData(userId, callback) {
  getUser(userId, function(user) {
    getOrders(user.id, function(orders) {
      getPayments(orders[0].id, function(payments) {
        callback({user, orders, payments});
      });
    });
  });
}

Ask:

"Modernize this to use async/await.
Here's our error handling pattern: [show example]"

Result:

async function fetchUserData(userId) {
  const user = await getUser(userId);
  const orders = await getOrders(user.id);
  const payments = orders.length > 0 ? await getPayments(orders[0].id) : [];
  return {user, orders, payments};
}

Type 4: Language Migration

Rewriting code in a different language:

Original (JavaScript):

function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

Ask:

"Convert this to Python, using memoization for performance.
Use our caching pattern from [show example]"

Result:

@functools.lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

The Refactoring Workflow

Step 1: Identify the Problem

Before refactoring, be clear about what’s wrong:

"This function is 50 lines and mixes concerns:
- Database queries
- Business logic
- External API calls
- Logging

It's hard to test and understand."

Better refactoring requests start with clear problems.

Step 2: Provide Context

Show how your project does refactoring:

"Here's how we structure our services: [example]
Here's our error handling: [example]
Here's our logging: [example]"

The AI follows your patterns.

Step 3: Ask for Refactoring

Be specific about the goal:

"Refactor this function to:
1. Separate data access (database layer)
2. Separate business logic (service layer)
3. Separate notification (external calls)

Here's the target structure: [example or ASCII diagram]"

Step 4: Review the Result

Before accepting:

- Are the new functions smaller and focused?
- Is each function single-responsibility?
- Are tests easier to write now?
- Does it match our patterns?
- Are there new edge cases?

Step 5: Iterate

"Good refactoring, but now errorHandling is missing
in the new notify function. Add it back."

"Also, can you generate tests for the new service layer?"

Refactoring Challenges and Solutions

ChallengeSolution
AI breaks existing testsProvide tests to the AI; ask it to maintain compatibility
New structure doesn’t match team styleShow example of how you structure code
AI loses important logic during refactoringReview carefully; ask for explanations of each function
AI suggests over-engineeringRequest “simple refactoring, no design patterns”

Code Transformation Tasks

Pattern 1: Conditional Simplification

Original:

if (user.role === 'admin') {
  if (user.isActive) {
    return true;
  } else {
    return false;
  }
} else {
  return false;
}

Ask:

"Simplify this conditional logic"

Result:

return user.role === 'admin' && user.isActive;

Pattern 2: Loop to Functional

Original:

result = []
for item in items:
    if item.price > 100:
        result.append(item.name.upper())
return result

Ask:

"Convert this loop to functional style (map/filter)"

Result:

return [item.name.upper() for item in items if item.price > 100]

Pattern 3: Class to Functions

Original:

class UserValidator {
  validateEmail(email) { ... }
  validatePassword(password) { ... }
  validatePhone(phone) { ... }
}

Ask:

"Convert this class to individual functions since we don't
maintain state. Here's our functional style: [example]"

Result:

export const validateEmail = (email) => { ... };
export const validatePassword = (password) => { ... };
export const validatePhone = (phone) => { ... };

Batch Refactoring

When you want to refactor many similar pieces:

Approach 1: Show One, Refactor Rest

"Here's one function refactored from X to Y pattern: [example]

Refactor these 10 similar functions using the same pattern:
[paste all 10]"

The AI learns the pattern from your example and applies it consistently.

Approach 2: Refactor, Test, Apply

For risky changes:

1. "Refactor these two functions as a test"
2. [Review the refactoring]
3. "The pattern looks good. Apply it to the other 8 functions"

This lets you catch issues before large-scale changes.

Safety: Refactoring Without Breaking Code

Before Refactoring

  1. Run your tests:

    npm test
    # All tests pass ✓
  2. Get the baseline:

    You: "What functionality does this code provide? List it."
    AI: [Lists 5 major features]
  3. Ask for preservation guarantee:

    "Refactor this maintaining all current behavior.
    Ensure it still supports: [list the features]"

After Refactoring

  1. Run tests immediately:

    npm test
    # Check for failures
  2. Verify behavior:

    - Manual testing of key workflows
    - Performance hasn't degraded
    - Error handling still works
  3. Code review:

    - Is it more readable?
    - Are there new edge cases?
    - Does it follow team patterns?

Real-World Example: Massive Refactor

Scenario: You have a 200-line validation function that’s become unmaintainable.

Step 1: Assess
"This validate() function is too large. It validates:
- Email format
- Password strength
- Phone number format
- Username uniqueness
- Credit card format

It's hard to test each part. Help me refactor."

Step 2: Get a Plan
AI: [Suggests breaking into 5 functions]
"I'd extract:
- validateEmail()
- validatePassword()
- validatePhone()
- validateUsername()
- validateCreditCard()

Each testable independently."

Step 3: Generate Refactored Code
"Generate these 5 functions from the original code:
[paste original 200-line function]

Pattern: [show example]
Error handling: [show example]"

Step 4: Generate Tests
"Now generate tests for each function to ensure
I'm not breaking anything:
[paste new functions]"

Step 5: Review and Accept
- Run tests (all pass)
- Review each function (looks good)
- Accept the refactoring
- Commit with message: "refactor: split validate() into focused functions"

When Not to Refactor

Sometimes refactoring isn’t worth it:

Don’t refactor if:

  • Code works and isn’t being modified frequently
  • The refactoring introduces more complexity
  • You’re close to a deadline
  • Tests don’t exist and would be expensive to add

Do refactor if:

  • You’re adding features to this code anyway
  • Multiple developers find it hard to understand
  • Tests exist to ensure you don’t break it
  • Code duplication is high

Exercises

  1. Extract Functions: Find a function in your codebase that does 3+ things. Ask the AI to extract it into smaller functions. Score the refactoring:

    • Is each function single-responsibility?
    • Are tests easier to write?
    • Does it match your code style?
  2. Rename for Clarity: Find code with unclear variable names. Ask the AI to rename variables and simplify conditionals. Compare before/after readability.

  3. Pattern Modernization: Find old-style code (callbacks, var, etc.). Ask the AI to modernize it to current best practices. Ensure tests still pass.

  4. Batch Refactoring: Find 5 similar functions that could be refactored consistently. Ask the AI to:

    • Refactor the first one as an example
    • Apply the same pattern to the other 4 Document the consistency achieved.
  5. Test-Driven Refactoring: Pick a function. Before refactoring:

    • Ask AI to write comprehensive tests
    • Run tests (should pass on original code)
    • Ask AI to refactor
    • Re-run tests (should still pass)
    • Verify the refactoring was truly safe