Refactoring Strategies

Production engineering guide for refactoring strategies covering patterns, implementation strategies, and operational best practices.

This page describes practical approaches to refactoring code and systems when changes are needed to improve maintainability, performance, or design without altering behavior. It applies during active development cycles, feature implementation, or when technical debt accumulates. These strategies focus on minimizing risk and maintaining functionality during transformation.

When to Apply Refactoring Strategies

Refactoring strategies apply when you need to change code structure, data models, or component interfaces without changing user-facing behavior. They are most useful during active development phases, especially before major releases or when preparing for new features. Common triggers include performance bottlenecks, complex logic that's hard to test, dependencies that cause cascading failures, and architecture decisions that have proven suboptimal.

Strategy: Extract Method

When a function grows too large or contains multiple responsibilities, extract methods to simplify logic. This approach reduces cognitive load and improves testability.

Example before:

def process_order(order):
    # Validate order
    if not order.customer_id:
        raise ValueError("Customer ID required")
    if not order.items:
        raise ValueError("Order must contain items")
    
    # Calculate totals
    subtotal = 0
    for item in order.items:
        subtotal += item.price * item.quantity
    
    tax_rate = 0.08
    total = subtotal * (1 + tax_rate)
    
    # Save to database
    db.save(order)
    return total

Example after:

def process_order(order):
    validate_order(order)
    subtotal = calculate_subtotal(order.items)
    total = calculate_total(subtotal)
    db.save(order)
    return total

def validate_order(order):
    if not order.customer_id:
        raise ValueError("Customer ID required")
    if not order.items:
        raise ValueError("Order must contain items")

def calculate_subtotal(items):
    subtotal = 0
    for item in items:
        subtotal += item.price * item.quantity
    return subtotal

def calculate_total(subtotal):
    tax_rate = 0.08
    return subtotal * (1 + tax_rate)

This strategy works with any language that supports function definitions and has no special flags or tools required beyond standard code editors.

Strategy: Move Method

When a method uses data from another class but belongs conceptually to that class, move it there. This reduces coupling between classes and improves encapsulation.

Example before:

class Order:
    def __init__(self, customer):
        self.customer = customer

class Customer:
    def __init__(self, name, address):
        self.name = name
        self.address = address

def calculate_shipping_cost(order):
    # Uses customer's address for shipping calculation
    return len(order.customer.address) * 0.1

Example after:

class Order:
    def __init__(self, customer):
        self.customer = customer

class Customer:
    def __init__(self, name, address):
        self.name = name
        self.address = address
    
    def calculate_shipping_cost(self):
        return len(self.address) * 0.1

This requires careful analysis of usage patterns and often involves updating all call sites to point to the new location.

Strategy: Rename Variable

Rename variables, functions, or classes to better reflect their purpose. This improves readability and reduces misunderstandings.

Example before:

def calculate(x, y):
    z = x * y
    return z + 10

Example after:

def calculate_total(base_value, multiplier):
    product = base_value * multiplier
    return product + 10

This strategy works across all languages and requires updating all references. It's particularly important when names become ambiguous or misleading.

Strategy: Replace Conditional with Polymorphism

When a function contains multiple conditional branches based on type, replace with polymorphic behavior to reduce complexity.

Example before:

def calculate_discount(customer):
    if customer.type == "premium":
        return customer.balance * 0.1
    elif customer.type == "gold":
        return customer.balance * 0.05
    else:
        return customer.balance * 0.02

Example after:

class Customer:
    def calculate_discount(self):
        return 0

class PremiumCustomer(Customer):
    def calculate_discount(self):
        return self.balance * 0.1

class GoldCustomer(Customer):
    def calculate_discount(self):
        return self.balance * 0.05

class RegularCustomer(Customer):
    def calculate_discount(self):
        return self.balance * 0.02

This approach requires careful handling of inheritance hierarchies and may involve updating existing object creation code.

Strategy: Introduce Parameter Object

When a function takes many parameters, group related ones into an object to simplify the interface.

Example before:

def create_user(name, email, phone, address, city, state, zip_code):
    # Implementation
    pass

Example after:

class UserCreationParams:
    def __init__(self, name, email, phone, address, city, state, zip_code):
        self.name = name
        self.email = email
        self.phone = phone
        self.address = address
        self.city = city
        self.state = state
        self.zip_code = zip_code

def create_user(params):
    # Implementation
    pass

This strategy is particularly useful for APIs that accept many configuration options.

Strategy: Extract Interface

When multiple classes implement similar behavior, extract a shared interface to define common contracts.

Example before:

class DatabaseConnection:
    def connect(self):
        # Implementation
        pass

class CacheConnection:
    def connect(self):
        # Implementation
        pass

Example after:

class ConnectionInterface:
    def connect(self):
        raise NotImplementedError

class DatabaseConnection(ConnectionInterface):
    def connect(self):
        # Implementation
        pass

class CacheConnection(ConnectionInterface):
    def connect(self):
        # Implementation
        pass

This strategy requires updating all existing implementations to comply with the new interface.

Strategy: Remove Dead Code

Identify and remove unused methods, classes, or variables that add complexity without value.

Example before:

def calculate_total(items):
    subtotal = 0
    for item in items:
        subtotal += item.price * item.quantity
    return subtotal

def unused_function():
    # This function is never called
    pass

Example after:

def calculate_total(items):
    subtotal = 0
    for item in items:
        subtotal += item.price * item.quantity
    return subtotal

This strategy works best when combined with static analysis tools or code coverage reports.

Strategy: Extract Class

When a class grows too large and handles multiple responsibilities, split it into smaller classes.

Example before:

class OrderProcessor:
    def __init__(self):
        self.orders = []
    
    def validate_order(self, order):
        # Validation logic
        pass
    
    def calculate_total(self, order):
        # Calculation logic
        pass
    
    def save_to_database(self, order):
        # Database logic
        pass
    
    def send_notification(self, order):
        # Notification logic
        pass

Example after:

class OrderValidator:
    def validate(self, order):
        # Validation logic
        pass

class OrderCalculator:
    def calculate_total(self, order):
        # Calculation logic
        pass

class OrderDatabase:
    def save(self, order):
        # Database logic
        pass

class OrderNotifier:
    def send_notification(self, order):
        # Notification logic
        pass

class OrderProcessor:
    def __init__(self):
        self.validator = OrderValidator()
        self.calculator = OrderCalculator()
        self.database = OrderDatabase()
        self.notifier = OrderNotifier()

This strategy requires updating all places that depend on the original class.

Strategy: Simplify Conditional Expressions

Replace complex conditional logic with simpler expressions or extract conditions to named variables.

Example before:

if order.total > 1000 and order.customer.type == "premium" and not order.has_discount:
    discount = order.total * 0.1
elif order.total > 500 and order.customer.type == "gold":
    discount = order.total * 0.05
else:
    discount = 0

Example after:

def is_eligible_for_large_discount(order):
    return order.total > 1000 and order.customer.type == "premium" and not order.has_discount

def is_eligible_for_medium_discount(order):
    return order.total > 500 and order.customer.type == "gold"

if is_eligible_for_large_discount(order):
    discount = order.total * 0.1
elif is_eligible_for_medium_discount(order):
    discount = order.total * 0.05
else:
    discount = 0

This improves readability and makes complex conditions easier to test.

Strategy: Inline Function

When a function is small and only called once, inline its implementation directly.

Example before:

def get_customer_name(customer):
    return customer.name

def process_customer(customer):
    name = get_customer_name(customer)
    return f"Processing {name}"

Example after:

def process_customer(customer):
    return f"Processing {customer.name}"

This reduces function call overhead and can simplify debugging.

Sharp Edges and Failure Modes

Refactoring introduces silent failures when dependencies are not updated. For example, renaming a method without updating all callers causes runtime errors that may only appear in production. Similarly, moving methods between classes without considering access modifiers can break encapsulation.

Polymorphism changes require careful attention to constructor calls and factory patterns. If objects are created directly instead of through interfaces, refactoring may not provide expected benefits.

Parameter object extraction assumes that all parameters are used consistently. If some parameters are only used in specific cases, the new interface may be overly complex.

Extracting classes without considering their dependencies can create circular imports or tight coupling between newly separated components.

This page was rewritten on 15 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.