Уровень 7 · Культура Глава 25 10 мин

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.

Лечение: конкретные бизнес-роли. UserManagerUserRegistrar + 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.

Foote & Yoder Big Ball of Mud, 1997

Лечение: долгосрочный рефакторинг. Идентифицировать 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 · закрепить

Проверьте себя

  1. Q1. Distributed Monolith — это...

  2. Q2. Cargo Cult Architecture — это...

  3. Q3. Anemic Domain Model — это всегда антипаттерн?

  4. Q4. Универсальный вопрос при выборе паттерна:

  5. Q5. Что общего у большинства архитектурных анти-паттернов?