Антипаттерны ООП
Технические антипаттерны — типичные ошибки на уровне кода и структуры классов. Не делать их — половина успеха. Особенно важно: уметь распознавать их в чужом коде на code review.
Источник списка: автор, OOP Anti-Patterns (Part 1).
Базовые антипаттерны кода
1. Дублирующийся код
Один и тот же фрагмент повторяется в разных местах с точностью до одной переменной/типа/if/имени.
Лечение: выделить в функцию/метод.
2. Hardcoded values / Magic numbers / Magic strings
if (status === 4) doSomething; // что такое 4?
Лечение: константы, enum, конфиг.
3. Cryptic code
Код, который человек не может прочитать без расшифровки.
const f = (a, b) => a > b ? a - b : b - a; // distance, видимо
Лечение: говорящие имена, маленькие функции.
4. Длинные функции/методы
«То, о чём мы много раз говорили — длинные функции, длинные процедуры, длинные методы. Очень типичная проблема.»
Лечение: декомпозиция, но не в один класс (см. ниже).
5. Длинные идентификаторы
Имя в 50 символов часто не информативно. Тип в имени (userListArrayOfObjectsForRendering) не помогает.
Лечение: короткие, говорящие имена. Тип — не в имени, а в системе типов.
Антипаттерны декомпозиции
6. Decomposition into one class
Разбить длинный метод недостаточно — нужно ещё разнести результат по разным классам.
«То, что разобьём метод на десять методов, ещё не значит, что эти методы нужно класть в один класс. Все десять в одном классе — это всё ещё антипаттерн».
7. Large class
Класс на 1000+ строк, на 20+ публичных методов. Обычно нарушает SRP. Лечение: разделить по ответственности.
8. Lazy class
Класс почти ничего не делает — обёртка над одним полем или одним методом. Лечение: заменить структурой или функцией.
9. Data clump
Группа из 3+ параметров регулярно ходит вместе через функции:
function send(host, port, user, pass, db) { /* ... */ }
function migrate(host, port, user, pass, db) { /* ... */ }
Лечение: обернуть в объект Connection.
10. Pass-through parameters
Параметры проходят через много функций, не используясь в промежуточных слоях. Лечение: контекст-объект, DI, замыкания.
11. Feature envy
Метод одного класса слишком интересуется полями другого класса.
class Order {
total {
return this.customer.discount * this.customer.country.tax * ...;
}
}
Лечение: перенести метод туда, где данные.
Антипаттерны наследования
12. Переусложнённые деревья наследования
Глубокая иерархия 5-7 уровней.
«Наследование замедляет исполнение, но это не существенно. Оно замедляет чтение этого кода — и тем более модификацию.»
Лечение: композиция вместо наследования, миксины, traits.
13. Yo-yo problem
Чтобы понять класс, надо прыгать вверх-вниз по иерархии (вверх — за поведением, вниз — за переопределениями). Лечение: плоская иерархия, dependency injection.
14. Abstraction inversion
Сложная фича построена на простой, потом её перереализуют через сложную (например, добавляют примитивы поверх Promise). Лечение: не «прятать» базовые средства языка.
Антипаттерны полиморфизма
15. Big switch case
Большой switch по типу объекта — признак отсутствующего полиморфизма.
«Как только видим большой switch case — задумываемся, может тут нужна Стратегия или другой паттерн, заменяющий switch».
// плохо
function area(shape) {
switch (shape.type) {
case 'circle': return ...;
case 'rect': return ...;
case 'triangle': return ...;
}
}
// хорошо — полиморфизм
class Circle { area { ... } }
class Rect { area { ... } }
shape.area;
16. Accumulate and fire
Перед вызовом метода накапливаются глобальные/полевые переменные:
this.userId = 1; this.action = 'delete'; this.target = 'X';
this.fire; // использует все три поля
Лечение: передавать аргументы в метод явно.
17. Refused bequest
Наследник игнорирует/переопределяет почти всё из родителя пустыми реализациями — значит, наследование не подходит.
Code smells при ООП
| Запах | Что означает | Что делать |
|---|---|---|
| Shotgun surgery | одно изменение трогает 10 классов | объединить связанные изменения |
| Divergent change | один класс меняется по разным причинам | разделить класс (SRP) |
| Parallel hierarchies | две иерархии меняются синхронно | объединить или применить bridge |
| Inappropriate intimacy | классы знают слишком много друг о друге | ослабить связанность |
| Temporary field | поле живёт только во время одного метода | сделать локальной переменной |
Источники
- Timur · OOP Anti-Patterns (Part 1) with JavaScript Examples (2019-11-21, 46 мин)