Anti-Patterns Compendium
Финальная глава: каталог всех антипаттернов курса в одном месте. Big Ball of Mud, Distributed Monolith, God Object, Anemic Domain, Service Locator, Shared Database. Признаки, причины, лечение.
TL;DR
- Каталог антипаттернов курса — по уровню (класс / слой / система / организация).
- Некоторые антипаттерны неизбежны на определённом этапе. Важно знать, когда пора уходить.
- Ошибка в организационном антипаттерне (Cargo Cult) — дороже кода.
- Всегда спрашивайте: «Что мы этим решаем?». Если ответа нет — вы копируете форму без содержания.
Как читать эту главу
Ниже — каталог антипаттернов, встречавшихся в курсе, плюс несколько классических. Для каждого:
- Признаки — как заметить.
- Почему появляется — обычно не от глупости, а от давления.
- Лечение — путь к нормальной архитектуре.
Некоторые антипаттерны — временные компромиссы, не «вечное зло». Знать, где сейчас находишься — важнее, чем клеймить.
На уровне класса
1. God Object / God Class
Признаки: класс на 1000+ строк, 30+ методов, знает про всё. UserManager, OrderService, DataProcessor.
Почему появляется: новый функционал прибавляют к «главному классу для этой сущности». Постепенно.
Лечение: декомпозиция по ответственностям (SRP). Обычно 5-10 маленьких классов вместо одного большого. См. главу Naming и smells.
2. Anemic Domain Model
Признаки: entities — только data (getters/setters), логика — в отдельных Service классах.
Почему появляется: ORM генерирует «entity» с get/set. Разработчики следуют шаблону. Логика уходит в сервисы.
Лечение: rich domain — методы в aggregate, приватные setters, инварианты защищены. См. главу 15.
Когда оправдан: сервис-оркестратор без бизнес-правил.
3. Feature Envy
Признаки: метод класса A постоянно тянет данные из B.
class Order:
def total_with_discount(self, customer):
return self.total * (1 - customer.get_discount_percent() *
customer.get_tier_bonus() *
customer.get_loyalty_factor())
Лечение: метод в B. customer.total_discount() — вычисляет один раз, возвращает финальную скидку.
4. Data Class
Признаки: класс только с полями + getters/setters, никакого поведения.
Лечение: либо это DTO (тогда @dataclass(frozen=True) и пусть будет), либо это должен быть rich aggregate.
5. Primitive Obsession
Признаки: str, int, float везде вместо Value Objects.
Лечение: Email, Money, PhoneNumber — Value Objects инкапсулируют валидацию.
6. Long Parameter List
Признаки: метод с 7+ параметрами.
Лечение: параметры кластеризуются — введите DTO / Command / Query.
7. Shotgun Surgery
Признаки: одно изменение требует править 8 файлов.
Причина: логика поля/правила размазана.
Лечение: локализовать в одно место (обычно в aggregate или configuration).
8. Weasel Words в именах
Признаки: Manager, Helper, Utils, Processor, Handler, Data, Info, Service.
Лечение: конкретные бизнес-роли. UserManager → UserRegistrar + UserAuthenticator + ProfileUpdater.
На уровне слоя
9. Repository = DAO
Признаки: getById, getAll, save, delete — CRUD-имена. Возвращает ORM-модели.
Лечение: бизнес-запросы (find_pending_orders_for_customer), возвращает aggregate. См. Repository Pattern.
10. Generic Repository
Признаки: Repository<T> с Get, Save, Delete для всех entities одинаково.
Лечение: отдельный Repository per aggregate. Разные бизнес-запросы.
11. IQueryable / Query Object leaking
Признаки: orderRepo.query().where(...) — SQL-подобный DSL в бизнес-логике.
Лечение: repository скрывает query language. Бизнесовые методы.
12. Service Locator
Признаки: ServiceLocator.get(IEmailService) внутри классов.
Лечение: явная DI через конструктор. См. главу Clean Architecture.
13. Mapper в use case
Признаки: внутри use case есть маппинг из формата вашего API в формат внешнего consumer’а.
Лечение: отдельный OutgoingMessageMapper в infrastructure. Легковесный ACL. См. case study в анонимизированном микросервисе.
14. Domain знает про ORM
Признаки: class Order(Base), Mapped[int] в domain-класс.
Лечение: Data Mapper или imperative mapping. См. главу Mappers и изоляция ORM.
15. Anemic Domain + Transaction Script
Признаки: OrderService.confirm_order(id) с 60 строками логики, Order — dict.
Лечение: rich model. order.confirm() внутри aggregate.
16. Утечка транспорта в use case
Признаки: use case принимает Request, возвращает JSONResponse.
Лечение: use case принимает Command, возвращает Result. HTTP-адаптер конвертирует.
17. Presenter в domain
Признаки: order.to_json(), order.serialize_for_api().
Лечение: отдельная response schema в interface слое.
На уровне системы
18. Big Ball of Mud
Признаки: архитектуры не существует. Каждый компонент знает про всех. Изменение в одном месте ломает 5 других.
Почему появляется: отсутствие явных границ + время + давление сроков.
A Big Ball of Mud is a haphazardly structured, sprawling, sloppy, duct-tape-and-baling-wire, spaghetti-code jungle.
Лечение: долгосрочный рефакторинг. Идентифицировать bounded contexts, вводить границы поэтапно.
19. Distributed Monolith
Признаки: множество микросервисов, но каждый деплой требует релиза N сервисов согласованно. Общая БД. Прямые sync-вызовы везде.
Лечение: переосмысление границ (bounded contexts), events вместо sync RPC, database per service.
20. Shared Database
Признаки: несколько микросервисов пишут в одни и те же таблицы.
Лечение: database per service, API-mediated access, events для sync.
21. Chatty Microservices
Признаки: один пользовательский запрос → 15+ межсервисных вызовов.
Лечение: BFF, batch API, CQRS с денормализованным read model, events вместо polling.
22. Dual Write без Outbox
Признаки: save() → publish() в use case. Регулярные жалобы «событие потеряно».
Лечение: Transactional Outbox + relay. См. главу Outbox.
23. Sync Everything
Признаки: все RPC — HTTP sync. Один медленный сервис = cascade failure.
Лечение: events для eventual операций, sagas для распределённых, circuit breakers.
24. Idempotency Ignored
Признаки: consumer обрабатывает PaymentCharged дважды при retry → двойное списание.
Лечение: idempotency keys, дедупликация через natural keys или processed_events table.
25. Leaky Abstractions
Joel Spolsky, 2002. Абстракция «пропускает» детали реализации.
Признаки: IQueryable в domain (протекает SQL), SqlException пробивается наверх, ORM-lazy-loading триггерит MissingGreenlet.
Лечение: плотные абстракции — контракт закрывает все реалистичные сценарии, ошибки переводятся в domain-level.
На уровне организации
26. Cargo Cult Architecture
Признаки: «в Netflix используют X — значит, нам нужно X».
Пример: микросервисы для команды из 3 человек. Kafka без объёмов. Event Sourcing для CRUD.
Лечение: трезвая оценка зачем. Разберитесь, какую проблему решает паттерн, есть ли эта проблема у вас.
27. Overengineering
Признаки: DDD + CQRS + Event Sourcing + microservices для сервиса на 5 endpoints.
Лечение: начинайте с малого. Layered → Hexagonal → добавление DDD только когда домен усложнился.
28. Second System Syndrome
Признаки: первая версия неаккуратна, но работает. Вторая — «сделаем как надо», через 3 года не готова.
Лечение: инкрементальный refactoring, а не переписывание с нуля. Strangler Fig pattern.
29. Not Invented Here
Признаки: свой ORM, свой веб-фреймворк, свой брокер.
Лечение: используйте стандартные инструменты. Ваш бизнес — не в изобретении web framework’а.
30. Frameworkitis / Fashion Driven
Признаки: каждый год — переезд на новый framework, новую БД, новый язык. Ничего не доделывается.
Лечение: технологические выборы имеют цену. Смена = только с явным бизнес-обоснованием.
Что общего
Большинство антипаттернов имеют общие корни:
Отсутствие явных границ. Модули, ответственности, контексты — размыты.
Копирование формы без содержания. «Так пишут в примерах» без понимания зачем.
Давление сроков как отговорка. «Потом отрефакторим» — обычно не отрефакторим.
Незнание альтернатив. Знаешь только гвоздь — всё вокруг молотки.
Универсальный вопрос: «Что мы этим решаем?». Если ответа нет — вы делаете что-то по инерции.
Как избежать
Изучайте не только «как», но и «почему». Каждый паттерн решает конкретную проблему. Понимаете какую — знаете, когда применять.
Не бойтесь inline / duplication на старте. DRY, применённый рано, склеивает то, что должно жить раздельно.
Явные границы важнее «правильных» имён. Bounded contexts, aggregates, ports — сначала.
Метрики поведения, а не покрытия. Хороший тест ловит регрессии бизнес-правил, а не измеряет процент строк.
Оставляйте память в коде. Комментарий «почему так» — иногда важнее любого рефакторинга.
Ревью — не только код. Обсуждайте архитектурные решения. ADR (Architecture Decision Records) — недорогой инструмент.
В твоём же коде
Анонимизированный микросервис в этом курсе — не образцовый, а честный. Есть анти-паттерны, есть хорошие решения. Финальный обзор:
Правильно:
- Hexagonal с портами через
Protocol. - Composition root на Dishka (не Service Locator).
- Async всё, где имеет смысл.
- Retry с exponential backoff.
Компромиссы:
- Anemic domain — оправдан для сервиса-оркестратора без бизнес-правил.
- Транзакция внутри Repository — «работает», но ограничивает эволюцию.
Реальные баги, которые стоит исправить:
- Mapper в use case (
_adapt_for_consumer) — утечка контракта consumer’а в бизнес-слой. - Dual write без Outbox — регулярный источник рассинхронизации.
- Нет идемпотентности — RabbitMQ может доставить сообщение дважды, downstream получит дубль.
Все три — темы отдельных глав курса.
Дальнейшее чтение
- Brian Foote, Joseph Yoder. Big Ball of Mud. 1997.
- Martin Fowler. AnemicDomainModel. 2003.
- Joel Spolsky. The Law of Leaky Abstractions. 2002.
- Chris Richardson. Microservices Patterns. Chapter 1 — микросервисные anti-patterns.
- Robert C. Martin. Clean Architecture. Chapters 4-7 — SOLID + основные smells.
- Michael Nygard. Release It! Классика про resiliency + stability anti-patterns.
- Kent Beck. Tidy First? — почему инкрементальный refactoring работает.
Что дальше
Это последняя глава курса. 25 глав, 7 уровней прогрессии. Что делать теперь:
- Читайте первоисточники. Курс сжимает и синтезирует, но детали — у авторов.
- Пересмотрите свой реальный код — что в нём хорошо, что стоит менять.
- Не бегите переписывать всё сразу. Один инкремент — одно улучшение. Rule of three.
- Обсуждайте с коллегами. Одна голова — одна точка зрения. Ubiquitous Language команды формируется через разговоры.
Спасибо, что дочитали.
Проверьте себя
Мини-quiz · закрепить
Проверьте себя
Q1. Distributed Monolith — это...
Q2. Cargo Cult Architecture — это...
Q3. Anemic Domain Model — это всегда антипаттерн?
Q4. Универсальный вопрос при выборе паттерна:
Q5. Что общего у большинства архитектурных анти-паттернов?