Back to Blog
3 min read

Winter Solstice Code Review: My Most Impactful Refactors of 2025

On the longest night of the year, let’s reflect on code improvements. Here are the refactoring patterns that delivered the biggest impact in my projects this year.

1. From God Objects to Domain Services

Before: A 2000-line OrderService handling everything

// The old way - one class doing everything
public class OrderService
{
    public async Task<Order> CreateOrder(...) { /* 200 lines */ }
    public async Task<Order> UpdateOrder(...) { /* 150 lines */ }
    public async Task ProcessPayment(...) { /* 300 lines */ }
    public async Task SendNotifications(...) { /* 100 lines */ }
    public async Task UpdateInventory(...) { /* 200 lines */ }
    // ... 15 more methods
}

After: Focused domain services

// Separated concerns
public class OrderCreationService
{
    private readonly IPaymentProcessor _payments;
    private readonly IInventoryService _inventory;
    private readonly INotificationService _notifications;

    public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
    {
        var order = Order.Create(request);

        await _inventory.ReserveItemsAsync(order.Items);
        await _payments.AuthorizeAsync(order.PaymentDetails);
        await _notifications.SendOrderConfirmationAsync(order);

        return order;
    }
}

Impact: Test coverage went from 40% to 85%, bug rate dropped 60%.

2. From Async Void to Proper Async Patterns

Before: Fire-and-forget causing silent failures

// Dangerous pattern
public async void ProcessBackgroundTask(string data)
{
    await _service.ProcessAsync(data); // Exceptions lost!
}

After: Proper async with error handling

public async Task ProcessBackgroundTaskAsync(string data, CancellationToken ct)
{
    try
    {
        await _service.ProcessAsync(data, ct);
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Background task failed for {Data}", data);
        await _deadLetterQueue.EnqueueAsync(data);
        throw; // Re-throw for proper error tracking
    }
}

Impact: Eliminated mysterious data loss, improved debugging.

3. From String Queries to Type-Safe Specifications

Before: SQL strings scattered throughout code

var orders = await _db.QueryAsync<Order>(
    "SELECT * FROM Orders WHERE CustomerId = @id AND Status = 'Active'",
    new { id = customerId });

After: Specification pattern with type safety

public class ActiveOrdersForCustomerSpec : Specification<Order>
{
    public ActiveOrdersForCustomerSpec(string customerId)
    {
        Query
            .Where(o => o.CustomerId == customerId)
            .Where(o => o.Status == OrderStatus.Active)
            .Include(o => o.Items)
            .OrderByDescending(o => o.CreatedAt);
    }
}

// Usage
var orders = await _repository.ListAsync(new ActiveOrdersForCustomerSpec(customerId));

Impact: Eliminated SQL injection risks, improved refactoring confidence.

4. From Primitive Obsession to Value Objects

Before: Strings everywhere

public void ProcessOrder(string orderId, string email, string amount) { }
// Easy to mix up parameters!

After: Strongly-typed value objects

public readonly record struct OrderId(Guid Value);
public readonly record struct Email(string Value)
{
    public static Email Create(string value) =>
        IsValid(value) ? new Email(value) : throw new InvalidEmailException(value);
}
public readonly record struct Money(decimal Amount, string Currency);

public void ProcessOrder(OrderId orderId, Email email, Money amount) { }
// Compiler catches mistakes

Impact: Eliminated entire categories of bugs, self-documenting code.

Key Takeaway

The best refactors aren’t about clever code - they’re about making the wrong thing impossible to do. Each of these patterns moved validation and constraints from runtime to compile time, catching bugs before they reach production.

Happy Winter Solstice! May your code grow cleaner as the days grow longer.

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.