Clean&Reactoring Entry Created: 05 Feb 2026 Updated: 05 Feb 2026

Fuse similar code together

In the fast-paced world of e-commerce, software requirements evolve daily. What starts as a simple checkout process quickly transforms into a complex web of discount rules, multiple shipping providers, and diverse payment gateways. Without disciplined refactoring, your codebase can quickly become a "Big Ball of Mud."

This article explores five essential refactoring patterns from the book Five Lines of Code, applied to a real-world e-commerce context using C#.

1. Unifying Similar Classes

The Problem: We often create specialized classes for variations that only differ by a few constants. This leads to code duplication and maintenance headaches.

Initial State (The "Code Smell"): Imagine having separate classes for different order types where only the delivery speed varies.

public class StandardOrder {
public int GetDeliveryDays() => 5;
public void Process() => Console.WriteLine("Processing standard order...");
}

public class ExpressOrder {
public int GetDeliveryDays() => 1;
public void Process() => Console.WriteLine("Processing express order...");
}

The Refactor: Instead of class-based variance, we use data-based variance. By converting constant methods into fields, we unify these into a single, parametric Order class.

public class Order {
private readonly int _deliveryDays;
private readonly string _type;

public Order(int deliveryDays, string type) {
_deliveryDays = deliveryDays;
_type = type;
}

public int GetDeliveryDays() => _deliveryDays;
public void Process() => Console.WriteLine($"Processing {_type} order...");
}

2. Combining Conditions (Combine Ifs)

The Problem: Duplicate logic across consecutive if statements with identical bodies makes the code harder to scan and update.

Initial State: Checking discount eligibility for different user segments.

if (user.IsGoldMember) {
ApplyDiscount();
} else if (order.TotalAmount > 1000) {
ApplyDiscount(); // Duplicated body
}

The Refactor: Merge the conditions using logical operators. This makes the "relationship" between the two conditions explicit.

if (user.IsGoldMember || order.TotalAmount > 1000) {
ApplyDiscount();
}

3. The Rule of Pure Conditions

The Problem: "Side effects" inside conditions (like logging to a DB or updating a variable) make debugging a nightmare. A condition should only ask a question, not change the world.

Bad (Side Effect): The method below checks stock but also modifies a log table.

if (stockService.CheckAndLogStock(productId)) {
PrepareShipment();
}

Good (Pure): Isolate the query from the command. The condition stays "Pure."

if (stockService.IsStockAvailable(productId)) {
stockService.LogCheck(productId); // Side effect moved outside
PrepareShipment();
}

4. Introducing the Strategy Pattern

The Problem: Using massive switch statements or if-else chains to handle different behaviors (like payment methods) violates the Open/Closed Principle.

The Refactor: Encapsulate the variance into separate strategy classes.

// Step 1: Define the abstraction
public interface IPaymentStrategy {
void Pay(decimal amount);
}

// Step 2: Implement concrete strategies
public class CreditCardPayment : IPaymentStrategy {
public void Pay(decimal amount) => Console.WriteLine($"Paid {amount} via Card.");
}

public class CryptoPayment : IPaymentStrategy {
public void Pay(decimal amount) => Console.WriteLine($"Paid {amount} via Bitcoin.");
}

// Step 3: Consume via Context
public class CheckoutManager {
private IPaymentStrategy _paymentStrategy;
public void SetPaymentMethod(IPaymentStrategy strategy) => _paymentStrategy = strategy;

public void CompleteOrder(decimal total) => _paymentStrategy.Pay(total);
}

5. Extract Interface from Implementation

The Problem: Over-engineering. Developers often create interfaces for every class before they are actually needed. The Rule: No interface with only one implementation.

Initial State: When you only work with one shipping provider, keep it simple.

public class ArasShippingService {
public void Send(Order order) { /* API Logic */ }
}

The Refactor: Only when a second provider (e.g., FedEx) is introduced should you extract the interface. This defers complexity until it provides actual value.

public interface IShippingService {
void Send(Order order);
}

public class ArasShippingService : IShippingService { ... }
public class FedExShippingService : IShippingService { ... }

Conclusion: Why Refactor?

Refactoring is not just about "cleaning up"; it is an investment in the longevity of your project:

  1. Maintenance: By unifying classes, you fix bugs in one place instead of five.
  2. Readability: Pure conditions and combined if statements reduce the cognitive load for new developers.
  3. Extensibility: Patterns like Strategy allow you to add new features (like a new payment type) without touching existing, tested code.
Share this lesson: