Функциональное программирование — не просто академическая теория. Это парадигма, превращающая императивный спагетти-код в декларативные, тестируемые и поддерживаемые решения.
Проблема императивного кода:
// Традиционный подход - может быть трудно читать и сложно тестировать
public OrderDto ProcessOrder(int orderId)
{
var order = _dbContext.Orders.Find(orderId);
if (order == null) throw new NotFoundException();
if (order.Status != "Pending")
throw new InvalidOperationException();
var customer = _dbContext.Customers.Find(order.CustomerId);
if (!customer.IsActive)
throw new InvalidOperationException();
var discount = customer.IsVip ? 0.1m :
order.Total > 1000 ? 0.05m : 0m;
order.FinalTotal = order.Total * (1 - discount);
_dbContext.SaveChanges();
return new OrderDto { ... };
}
Проблемы: смешанные обязанности, жёсткая связанность с БД, невозможность переиспользования, хрупкость при изменениях.
Композиционное решение:
public class OrderProcessingPipeline
{
private readonly Func<int, Task<Order?>> _loadOrder;
private readonly Func<Order, Task<Customer?>> _loadCustomer;
public async Task<Result<OrderDto, Error>> ProcessAsync(int orderId)
{
return await Result<Order, Error>
.FromNullable(await _loadOrder(orderId), Error.OrderNotFound)
.BindAsync(_loadCustomer)
.BindAsync(_validateStatus)
.BindAsync(_calculateDiscount)
.BindAsync(_persistOrder)
.MapAsync(_mapToDto);
}
}
Преимущества: единственная ответственность каждой функции, простота тестирования, композируемость, декларативный флоу.
Основа композиции — чистые функции без побочных эффектов:
public static class PricingFunctions
{
public static decimal CalculateDiscount(CustomerType type, decimal total) =>
type switch
{
CustomerType.Vip => total * 0.15m,
CustomerType.Premium => total * 0.10m,
_ => 0m
};
public static decimal ApplyTax(decimal amount, decimal rate) =>
amount * (1 + rate);
// Композиция
public static Func<CustomerType, decimal, decimal> CalculateFinalPrice =>
(type, total) => ApplyTax(total - CalculateDiscount(type, total), 0.08m);
}
Тест такой функции:
[Theory]
[InlineData(CustomerType.Vip, 1000, 918.00)]
public void CalculateFinalPrice_ReturnsExpected(CustomerType type, decimal total, decimal expected)
{
Assert.Equal(expected, PricingFunctions.CalculateFinalPrice(type, total));
}
Начните с малого: замените один цикл на Map, один if/throw на Result. Паттерны быстро станут естественными, и ваша кодовая база скажет спасибо.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминатор