Реальная Цена Абстракций в .NET. Окончание
Начало
Продолжение
Принимаем более взвешенные решения
Вот как следует понимать, когда абстракции стоят того:
1. Абстрактные политики, а не механизмы
Политики — решения, которые могут измениться: какой платёжный сервис использовать, как обрабатывать кэширование, стратегии повторных попыток для внешних вызовов.
Механизмы — стабильные детали реализации: LINQ в EF Core, конфигурация HttpClient, сериализация JSON.
Абстрагируйте политики, т.к. они обеспечивают гибкость. Не абстрагируйте механизмы — это уже стабильные API, которые редко меняются критически.
2. Дождитесь второй реализации
Если у вас только одна реализация, не поддавайтесь соблазну создать интерфейс. Одна реализация не оправдывает абстрагирование, это преждевременное обобщение, которое добавляет сложности без какой-либо пользы.
// 1: Начинаем с конкретного
public class EmailNotifier
{
public async Task SendAsync(
string to, string subject, string body)
{
// Реализация в SMTP
}
}
// 2: Нужны SMS? Теперь абстрагируемся
public interface INotifier
{
Task SendAsync(string to, string subject, string body);
}
public class EmailNotifier : INotifier { … }
public class SmsNotifier : INotifier { … }
Абстракция возникает естественным образом, когда она действительно нужна. Интерфейс раскрывается через реальные требования, а не воображаемые.
3. Реализации внутри, абстракции на границах
Внутри приложения отдавайте предпочтение конкретным типам. Используйте EF напрямую, настраивайте HttpClient как типизированные клиенты, работайте с сущностями домена. Вводите абстракции только там, где система взаимодействует с внешним миром: внешними API, сторонними SDK, инфраструктурными сервисами. Именно там изменения наиболее вероятны, и там абстракции оправдывают себя.
Рефакторинг плохих абстракций
Регулярно задавайте себе вопрос: если я уберу эту абстракцию, код станет проще или сложнее? Если удаление интерфейса или сервисного уровня сделает код более понятным и прямолинейным, эта абстракция, вероятно, стоит больше, чем приносит пользы. Не бойтесь удалять ненужные уровни.
Выявив проблемные абстракции, вот как их безопасно удалить:
1. Определите реальных потребителей. Кому на самом деле нужна абстракция?
2. Встройте интерфейс. Замените абстрактные вызовы конкретными реализациями.
3. Удалите обёртку - ненужные косвенные обращения.
4. Упростите вызывающий код. Воспользуйтесь возможностями конкретного API.
Например, замените репозиторий прямым использованием EF:
// До: Скрыто за репозиторием
var users = await _userRepo
.GetActiveUsersWithRecentOrders();
// После: Прямой запрос
var users = await _context.Users
.Where(u => u.IsActive)
.Where(u => u.Orders.Any(o => o.CreatedAt > DateTime.Now.AddDays(-30)))
.Include(u => u.Orders.Take(5))
.ToListAsync();
Конкретная версия более явно описывает, какие данные она извлекает и как. Если вам нужен один и тот же запрос в нескольких местах, вы можете перенести его в метод расширения, чтобы сделать его общим.
Итого
Абстракции — мощные инструменты для управления сложностью и изменениями, но они не бесплатны. Каждая добавляет косвенные издержки, когнитивные накладные расходы и нагрузку на поддержку. Самая чистая архитектура — та, где каждый слой имеет чёткое и обоснованное назначение.
Прежде чем добавлять следующую абстракцию, спросите себя:
- Я абстрагирую политику или просто механизм?
- У меня две реализации, или я размышляю о будущих потребностях?
- Сделает ли это мою систему более адаптивной или просто более сложной для понимания?
- Если я уберу этот слой, станет ли код проще?
Помните: абстракции — это кредиты, по которым со временем начисляются проценты. Убедитесь, что вы берёте их по правильным причинам, а не просто по привычке.
Источник: https://www.milanjovanovic.tech/blog/the-real-cost-of-abstractions-in-dotnet