Антипаттерны
Антипаттерн — повторяющееся решение, которое выглядит удобным локально, но регулярно создаёт предсказуемые проблемы. Это не ярлык для любого кода, который мне не нравится: нужны контекст, наблюдаемые последствия и более подходящая альтернатива.
God Object
Один класс знает и делает слишком много:
controller
+ validation
+ SQL
+ HTTP clients
+ mapping
+ notificationsПризнаки:
- изменение любой функции затрагивает класс;
- слишком много dependencies;
- tests требуют настроить всю систему;
- методы работают с несвязанными группами полей.
Разделение идёт по use cases и ответственности, а не просто по числу строк.
Big Ball of Mud
У системы нет читаемых границ зависимостей. Любой модуль обращается к любому, database tables становятся общим API, а локальное изменение вызывает неизвестные эффекты.
Лечить это «полной переписью» рискованно. Сначала выделяют наблюдаемую границу: package/module/API, тестируют её и постепенно уменьшают обходные связи.
Service Locator
Класс сам получает dependency из глобального context:
PaymentClient client =
Context.getBean(PaymentClient.class);Зависимость не видна в constructor, а unit test зависит от container. Для обязательных collaborators предпочитаю constructor injection.
Primitive Obsession
Разные сущности представлены одинаковыми String/long, поэтому compiler не различает их:
void transfer(long source, long target, BigDecimal value)Value objects делают контракт явнее:
void transfer(AccountId source, AccountId target, Money value)Shotgun Surgery
Одно бизнес-изменение требует править множество несвязанных мест. Причиной бывает дублирование правила или неверная граница модуля.
Перед рефакторингом ищу все реализации и формулирую единый инвариант. Механически объединить код в «Utils» недостаточно.
Golden Hammer
Знакомый инструмент используется для любой задачи: Kafka для синхронного запроса, inheritance для всякого расширения, microservice для маленького автономного модуля.
Проверка — сравнить хотя бы одно более простое решение и назвать требование, ради которого нужна дополнительная сложность.
Swallowed exception
Ошибка логируется и превращается в успешное завершение:
try {
process();
} catch (Exception exception) {
log.error("failed", exception);
}В транзакции это может привести к commit частичного результата. Реальный пример: Кейс исключение было проглочено внутри транзакции.
Distributed Monolith
Сервисы развёрнуты отдельно, но должны обновляться вместе, используют общую схему БД и образуют длинную синхронную цепочку. Появилась сетевая сложность без независимости.
Как разбираю подозрение на антипаттерн
- Какой конкретный симптом наблюдается?
- Какое решение его создаёт?
- В каком контексте решение всё-таки допустимо?
- Какой минимальный шаг уменьшит проблему?
- Чем проверю, что стало лучше?
Вопросы для собеседования
- Любой Singleton — антипаттерн? Нет; проблема чаще в скрытом global state и неясном lifecycle.
- Большой класс автоматически God Object? Нет, важнее число несвязанных ответственностей.
- Почему Service Locator осложняет тест? Dependencies скрыты и получаются из global container.
- Как исправлять Big Ball of Mud? Постепенно вводить проверяемые границы, а не начинать слепой rewrite.