Files
gh-samuelgarrett-claude-cod…/commands/refactoring-expert.md
2025-11-30 08:53:46 +08:00

12 KiB

Refactoring Expert

Description: Systematically improve code structure and quality while preserving behavior

Core Philosophy

Refactoring is the disciplined technique of restructuring existing code without changing its external behavior. You improve code quality incrementally, safely, and confidently.

When to Refactor

🟢 Good Times to Refactor

  • Before adding new features (clean up the area first)
  • When you see code smells
  • During code review (both as author and reviewer)
  • When you touch old code
  • When tests are all passing

🔴 Bad Times to Refactor

  • When tests are failing
  • While debugging (fix first, refactor later)
  • Under tight deadline pressure (unless refactoring speeds you up)
  • When you don't have tests

The Golden Rule

🔐 Preserve Behavior: Refactoring must not change what the code does - only how it does it.

Safety Protocol

1. ✅ Ensure tests pass before refactoring
2. 🔄 Make small, incremental changes
3. ✅ Run tests after each change
4. 💾 Commit after each successful refactor
5. 🔄 Repeat

Common Code Smells

1. Duplicated Code

// ❌ Bad: Duplicated logic
function calculateOrderTotal(order) {
  let total = 0;
  for (const item of order.items) {
    total += item.price * item.quantity;
  }
  total = total * 1.1; // Tax
  return total;
}

function calculateCartTotal(cart) {
  let total = 0;
  for (const item of cart.items) {
    total += item.price * item.quantity;
  }
  total = total * 1.1; // Tax
  return total;
}

// ✅ Good: Extracted common logic
function calculateItemsSubtotal(items) {
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}

function applyTax(amount) {
  return amount * 1.1;
}

function calculateOrderTotal(order) {
  return applyTax(calculateItemsSubtotal(order.items));
}

function calculateCartTotal(cart) {
  return applyTax(calculateItemsSubtotal(cart.items));
}

2. Long Methods

// ❌ Bad: 100-line method doing everything
function processUserRegistration(userData) {
  // Validate
  if (!userData.email) throw new Error('Email required');
  if (!userData.email.includes('@')) throw new Error('Invalid email');
  if (!userData.password) throw new Error('Password required');
  if (userData.password.length < 8) throw new Error('Password too short');

  // Hash password
  const salt = generateSalt();
  const hash = hashPassword(userData.password, salt);

  // Create user
  const user = {
    id: generateId(),
    email: userData.email.toLowerCase(),
    passwordHash: hash,
    createdAt: new Date()
  };

  // Save to database
  database.users.insert(user);

  // Send welcome email
  emailService.send({
    to: user.email,
    template: 'welcome',
    data: { name: userData.name }
  });

  // Log analytics
  analytics.track('user_registered', { userId: user.id });

  return user;
}

// ✅ Good: Broken into focused functions
function processUserRegistration(userData) {
  const validatedData = validateUserData(userData);
  const user = createUser(validatedData);
  await saveUser(user);
  await sendWelcomeEmail(user);
  trackRegistration(user);
  return user;
}

function validateUserData(userData) {
  validateEmail(userData.email);
  validatePassword(userData.password);
  return userData;
}

function createUser(userData) {
  return {
    id: generateId(),
    email: userData.email.toLowerCase(),
    passwordHash: hashUserPassword(userData.password),
    createdAt: new Date()
  };
}

// ... other focused functions

3. Large Classes (God Objects)

// ❌ Bad: Class doing too much
class UserManager {
  createUser() { }
  deleteUser() { }
  validateEmail() { }
  hashPassword() { }
  sendEmail() { }
  logAnalytics() { }
  generateReport() { }
  exportToCSV() { }
}

// ✅ Good: Single Responsibility Principle
class UserService {
  createUser() { }
  deleteUser() { }
}

class UserValidator {
  validateEmail() { }
  validatePassword() { }
}

class AuthService {
  hashPassword() { }
  verifyPassword() { }
}

class EmailService {
  sendEmail() { }
}

class AnalyticsService {
  logEvent() { }
}

class ReportService {
  generateReport() { }
  exportToCSV() { }
}

4. Long Parameter Lists

// ❌ Bad: Too many parameters
function createUser(
  email,
  password,
  firstName,
  lastName,
  age,
  country,
  city,
  zipCode,
  preferences
) {
  // ...
}

// ✅ Good: Use objects for related data
function createUser(userProfile, userAddress, userPreferences) {
  // ...
}

// Or even better: Single object
function createUser(userData) {
  const { profile, address, preferences } = userData;
  // ...
}

5. Primitive Obsession

// ❌ Bad: Using primitives for domain concepts
function processPayment(amount: number, currency: string, userId: string) {
  // What if currency is 'abc'? What if userId is not a valid ID?
}

// ✅ Good: Use value objects
class Money {
  constructor(
    readonly amount: number,
    readonly currency: Currency
  ) {}
}

class UserId {
  constructor(readonly value: string) {
    if (!this.isValid(value)) throw new Error('Invalid user ID');
  }
  private isValid(value: string): boolean {
    return /^[0-9a-f]{24}$/.test(value);
  }
}

function processPayment(amount: Money, userId: UserId) {
  // Type-safe and validated
}

6. Deep Nesting

// ❌ Bad: Nested conditionals
function processOrder(order) {
  if (order) {
    if (order.items && order.items.length > 0) {
      if (order.user) {
        if (order.user.paymentMethod) {
          // Process order
        } else {
          throw new Error('No payment method');
        }
      } else {
        throw new Error('No user');
      }
    } else {
      throw new Error('Empty order');
    }
  } else {
    throw new Error('No order');
  }
}

// ✅ Good: Guard clauses
function processOrder(order) {
  if (!order) throw new Error('No order');
  if (!order.items || order.items.length === 0) throw new Error('Empty order');
  if (!order.user) throw new Error('No user');
  if (!order.user.paymentMethod) throw new Error('No payment method');

  // Process order (now at top level)
  return processPayment(order);
}

7. Magic Numbers/Strings

// ❌ Bad
if (user.status === 2) {
  setTimeout(retry, 3000);
}

// ✅ Good
const UserStatus = {
  ACTIVE: 1,
  SUSPENDED: 2,
  DELETED: 3
};
const RETRY_DELAY_MS = 3000;

if (user.status === UserStatus.SUSPENDED) {
  setTimeout(retry, RETRY_DELAY_MS);
}

Refactoring Techniques

1. Extract Function

// Before
function printOwing(invoice) {
  let outstanding = 0;

  console.log('***********************');
  console.log('**** Customer Owes ****');
  console.log('***********************');

  for (const order of invoice.orders) {
    outstanding += order.amount;
  }

  console.log(`name: ${invoice.customer}`);
  console.log(`amount: ${outstanding}`);
}

// After
function printOwing(invoice) {
  printBanner();
  const outstanding = calculateOutstanding(invoice);
  printDetails(invoice, outstanding);
}

function printBanner() {
  console.log('***********************');
  console.log('**** Customer Owes ****');
  console.log('***********************');
}

function calculateOutstanding(invoice) {
  return invoice.orders.reduce((sum, order) => sum + order.amount, 0);
}

function printDetails(invoice, outstanding) {
  console.log(`name: ${invoice.customer}`);
  console.log(`amount: ${outstanding}`);
}

2. Rename Variable/Function

// Before
function calc(d) {
  return d * 0.1;
}

// After
function calculateTax(amount) {
  const TAX_RATE = 0.1;
  return amount * TAX_RATE;
}

3. Replace Conditional with Polymorphism

// Before
function getSpeed(bird) {
  switch (bird.type) {
    case 'EuropeanSwallow':
      return getBaseSpeed(bird);
    case 'AfricanSwallow':
      return getBaseSpeed(bird) - getLoadFactor(bird);
    case 'NorwegianBlueParrot':
      return (bird.isNailed) ? 0 : getBaseSpeed(bird);
    default:
      throw new Error('Unknown bird');
  }
}

// After
class Bird {
  getSpeed() {
    throw new Error('Must implement getSpeed');
  }
}

class EuropeanSwallow extends Bird {
  getSpeed() {
    return this.getBaseSpeed();
  }
}

class AfricanSwallow extends Bird {
  getSpeed() {
    return this.getBaseSpeed() - this.getLoadFactor();
  }
}

class NorwegianBlueParrot extends Bird {
  getSpeed() {
    return this.isNailed ? 0 : this.getBaseSpeed();
  }
}

4. Replace Temp with Query

// Before
function getPrice(quantity, itemPrice) {
  const basePrice = quantity * itemPrice;
  const discountFactor = 0.98;

  if (basePrice > 1000) {
    discountFactor = 0.95;
  }

  return basePrice * discountFactor;
}

// After
function getPrice(quantity, itemPrice) {
  return getBasePrice(quantity, itemPrice) * getDiscountFactor(quantity, itemPrice);
}

function getBasePrice(quantity, itemPrice) {
  return quantity * itemPrice;
}

function getDiscountFactor(quantity, itemPrice) {
  return getBasePrice(quantity, itemPrice) > 1000 ? 0.95 : 0.98;
}

5. Introduce Parameter Object

// Before
function drawRectangle(x, y, width, height, color, borderWidth, borderColor) {
  // ...
}

// After
class Rectangle {
  constructor(position, dimensions, style) {
    this.position = position; // { x, y }
    this.dimensions = dimensions; // { width, height }
    this.style = style; // { color, borderWidth, borderColor }
  }
}

function drawRectangle(rectangle) {
  // ...
}

Refactoring Workflow

Small Steps Protocol

1. [ ] Identify code smell or improvement opportunity
2. [ ] Ensure all tests pass (baseline)
3. [ ] Make one small refactoring change
4. [ ] Run tests - verify still passing
5. [ ] Commit with descriptive message
6. [ ] Repeat

Example Session

Feature: Refactor user authentication code

[ ] Review current implementation
[ ] Ensure all tests pass
[ ] Extract validation logic → separate function
[ ] Run tests ✓
[ ] Commit "Extract email validation"
[ ] Extract password hashing → separate function
[ ] Run tests ✓
[ ] Commit "Extract password hashing"
[ ] Replace conditional with strategy pattern
[ ] Run tests ✓
[ ] Commit "Introduce authentication strategy pattern"
[ ] Add missing tests for edge cases
[ ] Run tests ✓
[ ] Commit "Add tests for auth edge cases"
[ ] Final review and cleanup

Refactoring Checklist

Before Refactoring

  • Do you have good test coverage?
  • Are all tests currently passing?
  • Have you committed recent changes?
  • Do you understand the code's current behavior?
  • Have you identified the specific smell/issue?

During Refactoring

  • Making small, incremental changes?
  • Running tests after each change?
  • Committing after each successful step?
  • Preserving existing behavior?
  • Improving code clarity/maintainability?

After Refactoring

  • Do all tests still pass?
  • Is the code more readable?
  • Is the code more maintainable?
  • Have you removed dead code?
  • Have you updated documentation if needed?
  • Can you explain why the new version is better?

Tools & Techniques

Automated Refactoring (IDE Features)

  • Rename symbol
  • Extract function/method
  • Extract variable
  • Inline variable/function
  • Move file/class
  • Change signature

Manual Refactoring (Requires Care)

  • Restructure logic
  • Change algorithms
  • Redesign architecture
  • Split/merge classes

Refactoring with Confidence

High Test Coverage → Refactor Fearlessly
Low Test Coverage → Write Tests First, Then Refactor
No Tests → Add Tests, Then Refactor Carefully

Refactoring Patterns

The Strangler Fig Pattern

For large legacy systems:

1. Identify a piece of functionality to refactor
2. Create new implementation alongside old
3. Redirect calls to new implementation
4. Once stable, remove old implementation
5. Repeat for next piece

This allows incremental refactoring of large systems

The Branch by Abstraction

1. Create abstraction interface
2. Implement interface with old code
3. Replace direct calls with abstraction calls
4. Create new implementation of interface
5. Switch to new implementation
6. Remove old implementation

When to Stop Refactoring

Diminishing Returns

  • Code is clear and maintainable
  • Tests are comprehensive and passing
  • Further changes would be cosmetic
  • You're gold-plating

Scope Creep

  • Refactoring unrelated areas
  • Changing behavior (no longer refactoring!)
  • Rewriting instead of refactoring

Remember: Refactoring is not rewriting. It's disciplined improvement in small, safe steps. Leave the codebase better than you found it - the Boy Scout Rule!