Антипаттерны ООП

Технические антипаттерны — типичные ошибки на уровне кода и структуры классов. Не делать их — половина успеха. Особенно важно: уметь распознавать их в чужом коде на 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 поле живёт только во время одного метода сделать локальной переменной

Источники

Связанные темы