Каталог запахів коду
Комплексний довідник запахів коду на основі книги Мартіна Фаулера Refactoring (2-ге видання). Запахи коду — це симптоми глибших проблем; вони вказують на те, що щось може бути не так з дизайном вашого коду.
«Запах коду — це поверхнева ознака, яка зазвичай відповідає глибшій проблемі в системі.» — Мартін Фаулер
Роздуті елементи (Bloaters)
Запахи коду, що представляють щось, що розрослося занадто великим для ефективної роботи.
Long Method (Довгий метод)
Ознаки:
- Метод перевищує 30-50 рядків
- Потрібно прокручувати, щоб побачити весь метод
- Кілька рівнів вкладеності
- Коментарі пояснюють, що роблять секції
Чому це погано:
- Важко зрозуміти
- Складно тестувати ізольовано
- Зміни мають непередбачувані наслідки
- Дубльована логіка ховається всередині
Рефакторинги:
- Extract Method
- Replace Temp with Query
- Introduce Parameter Object
- Replace Method with Method Object
- Decompose Conditional
Приклад (до):
function processOrder(order) {
// Валідація замовлення (20 рядків)
if (!order.items) throw new Error('No items');
if (order.items.length === 0) throw new Error('Empty order');
// ... ще валідація
// Обчислення підсумків (30 рядків)
let subtotal = 0;
for (const item of order.items) {
subtotal += item.price * item.quantity;
}
// ... податок, доставка, знижки
// Надсилання сповіщень (20 рядків)
// ... логіка email
}Приклад (після):
function processOrder(order) {
validateOrder(order);
const totals = calculateOrderTotals(order);
sendOrderNotifications(order, totals);
return { order, totals };
}Large Class (Великий клас)
Ознаки:
- Клас має багато полів екземпляра (>7-10)
- Клас має багато методів (>15-20)
- Назва класу розмита (Manager, Handler, Processor)
- Методи не використовують усі поля екземпляра
Чому це погано:
- Порушує принцип єдиної відповідальності
- Важко тестувати
- Зміни поширюються на неповʼязані функції
- Складно повторно використовувати частини
Рефакторинги:
- Extract Class
- Extract Subclass
- Extract Interface
Виявлення:
Рядків коду > 300
Кількість методів > 15
Кількість полів > 10Primitive Obsession (Одержимість примітивами)
Ознаки:
- Використання примітивів для доменних концепцій (string для email, int для грошей)
- Масиви примітивів замість обʼєктів
- Рядкові константи для кодів типів
- Магічні числа/рядки
Чому це погано:
- Немає валідації на рівні типу
- Логіка розкидана по кодовій базі
- Легко передати неправильні значення
- Відсутні доменні концепції
Рефакторинги:
- Replace Primitive with Object
- Replace Type Code with Class
- Replace Type Code with Subclasses
- Replace Type Code with State/Strategy
Приклад (до):
const user = {
email: 'john@example.com', // Просто рядок
phone: '1234567890', // Просто рядок
status: 'active', // Магічний рядок
balance: 10050 // Копійки як ціле число
};Приклад (після):
const user = {
email: new Email('john@example.com'),
phone: new PhoneNumber('1234567890'),
status: UserStatus.ACTIVE,
balance: Money.cents(10050)
};Long Parameter List (Довгий список параметрів)
Ознаки:
- Методи з 4+ параметрами
- Параметри, що завжди зʼявляються разом
- Булеві прапорці, що змінюють поведінку методу
- Часте передавання null/undefined
Чому це погано:
- Важко викликати правильно
- Плутанина з порядком параметрів
- Вказує, що метод робить занадто багато
- Важко додавати нові параметри
Рефакторинги:
- Introduce Parameter Object
- Preserve Whole Object
- Replace Parameter with Method Call
- Remove Flag Argument
Приклад (до):
function createUser(firstName, lastName, email, phone,
street, city, state, zip,
isAdmin, isActive, createdBy) {
// ...
}Приклад (після):
function createUser(personalInfo, address, options) {
// personalInfo: { firstName, lastName, email, phone }
// address: { street, city, state, zip }
// options: { isAdmin, isActive, createdBy }
}Data Clumps (Групи даних)
Ознаки:
- Ті самі 3+ поля зʼявляються разом повторно
- Параметри, що завжди подорожують разом
- Класи з підмножинами полів, що належать один одному
Чому це погано:
- Дубльована логіка обробки
- Відсутня абстракція
- Важче розширювати
- Вказує на прихований клас
Рефакторинги:
- Extract Class
- Introduce Parameter Object
- Preserve Whole Object
Приклад:
// Група даних: координати (x, y, z)
function movePoint(x, y, z, dx, dy, dz) { }
function scalePoint(x, y, z, factor) { }
function distanceBetween(x1, y1, z1, x2, y2, z2) { }
// Витяг класу Point3D
class Point3D {
constructor(x, y, z) { }
move(delta) { }
scale(factor) { }
distanceTo(other) { }
}Зловживання обʼєктною орієнтацією (OO Abusers)
Запахи, що вказують на неповне або некоректне використання принципів ООП.
Switch Statements (Оператори Switch)
Ознаки:
- Довгі ланцюжки switch/case або if/else
- Той самий switch у кількох місцях
- Switch за кодами типів
- Додавання нових випадків вимагає змін всюди
Чому це погано:
- Порушує принцип відкритості/закритості
- Зміни поширюються на всі місця з switch
- Важко розширювати
- Часто вказує на відсутній поліморфізм
Рефакторинги:
- Replace Conditional with Polymorphism
- Replace Type Code with Subclasses
- Replace Type Code with State/Strategy
Приклад (до):
function calculatePay(employee) {
switch (employee.type) {
case 'hourly':
return employee.hours * employee.rate;
case 'salaried':
return employee.salary / 12;
case 'commissioned':
return employee.sales * employee.commission;
}
}Приклад (після):
class HourlyEmployee {
calculatePay() {
return this.hours * this.rate;
}
}
class SalariedEmployee {
calculatePay() {
return this.salary / 12;
}
}Temporary Field (Тимчасове поле)
Ознаки:
- Поля екземпляра, що використовуються лише в деяких методах
- Поля, що встановлюються умовно
- Складна ініціалізація для певних випадків
Чому це погано:
- Плутанина — поле існує, але може бути null
- Важко зрозуміти стан обʼєкта
- Вказує на приховану умовну логіку
Рефакторинги:
- Extract Class
- Introduce Null Object
- Replace Temp Field with Local
Refused Bequest (Відхилена спадщина)
Ознаки:
- Підклас не використовує успадковані методи/дані
- Підклас перевизначає, щоб нічого не робити
- Успадкування використовується для повторного використання коду, а не для відношення IS-A
Чому це погано:
- Неправильна абстракція
- Порушує принцип підстановки Лісков
- Оманлива ієрархія
Рефакторинги:
- Push Down Method/Field
- Replace Subclass with Delegate
- Replace Inheritance with Delegation
Alternative Classes with Different Interfaces (Альтернативні класи з різними інтерфейсами)
Ознаки:
- Два класи, що роблять схожі речі
- Різні назви методів для тієї самої концепції
- Можуть використовуватися взаємозамінно
Чому це погано:
- Дубльовані реалізації
- Немає спільного інтерфейсу
- Важко перемикатися між ними
Рефакторинги:
- Rename Method
- Move Method
- Extract Superclass
- Extract Interface
Перешкоди для змін (Change Preventers)
Запахи, що ускладнюють зміни — зміна однієї речі вимагає зміни багатьох інших.
Divergent Change (Дивергентна зміна)
Ознаки:
- Один клас змінюється з кількох різних причин
- Зміни в різних областях запускають редагування того самого класу
- Клас є «God-класом»
Чому це погано:
- Порушує принцип єдиної відповідальності
- Висока частота змін
- Конфлікти злиття
Рефакторинги:
- Extract Class
- Extract Superclass
- Extract Subclass
Приклад: Клас User змінюється через:
- Зміни автентифікації
- Зміни профілю
- Зміни білінгу
- Зміни сповіщень
→ Витягти: AuthService, ProfileService, BillingService, NotificationService
Shotgun Surgery (Хірургія дробовиком)
Ознаки:
- Одна зміна вимагає редагування в багатьох класах
- Мала функція потребує правок у 10+ файлах
- Зміни розкидані, важко знайти всі
Чому це погано:
- Легко пропустити місце
- Високий звʼязок (coupling)
- Зміни схильні до помилок
Рефакторинги:
- Move Method
- Move Field
- Inline Class
Виявлення: Шукайте: додавання одного поля вимагає змін у >5 файлах.
Parallel Inheritance Hierarchies (Паралельні ієрархії успадкування)
Ознаки:
- Створення підкласу в одній ієрархії вимагає підкласу в іншій
- Префікси класів збігаються (напр.,
DatabaseOrder,DatabaseProduct)
Чому це погано:
- Подвійне обслуговування
- Звʼязок між ієрархіями
- Легко забути одну сторону
Рефакторинги:
- Move Method
- Move Field
- Усунути одну ієрархію
Непотрібне (Dispensables)
Щось непотрібне, що слід видалити.
Comments (Надмірні коментарі)
Ознаки:
- Коментарі, що пояснюють, що робить код
- Закоментований код
- TODO/FIXME, що залишаються назавжди
- Вибачення в коментарях
Чому це погано:
- Коментарі брешуть (розсинхронізуються)
- Код має бути самодокументованим
- Мертвий код створює плутанину
Рефакторинги:
- Extract Method (назва пояснює що)
- Rename (ясність без коментарів)
- Видалити закоментований код
- Introduce Assertion
Хороші vs погані коментарі:
// ПОГАНО: Пояснює що
// Цикл по користувачах і перевірка чи активні
for (const user of users) {
if (user.status === 'active') { }
}
// ДОБРЕ: Пояснює чому
// Лише активні — неактивні обробляються задачею очищення
const activeUsers = users.filter(u => u.isActive);Duplicate Code (Дубльований код)
Ознаки:
- Той самий код у кількох місцях
- Схожий код з малими варіаціями
- Патерни копі-пасту
Чому це погано:
- Виправлення помилок потрібне в кількох місцях
- Ризик неконсистентності
- Роздута кодова база
Рефакторинги:
- Extract Method
- Extract Class
- Pull Up Method (в ієрархіях)
- Form Template Method
Правило виявлення: Будь-який код, дубльований 3+ разів, слід витягти.
Lazy Class (Ледачий клас)
Ознаки:
- Клас не робить достатньо для виправдання свого існування
- Обгортка без доданої цінності
- Результат надмірної інженерії
Чому це погано:
- Накладні витрати обслуговування
- Непотрібна непрямість
- Складність без користі
Рефакторинги:
- Inline Class
- Collapse Hierarchy
Dead Code (Мертвий код)
Ознаки:
- Недосяжний код
- Невикористані змінні/методи/класи
- Закоментований код
- Код за неможливими умовами
Чому це погано:
- Плутанина
- Тягар обслуговування
- Уповільнює розуміння
Рефакторинги:
- Remove Dead Code
- Safe Delete
Виявлення:
# Шукати невикористані експорти
# Шукати невикористані функції
# Попередження IDE "unused"Speculative Generality (Спекулятивна загальність)
Ознаки:
- Абстрактні класи з одним підкласом
- Невикористані параметри «на майбутнє»
- Методи, що лише делегують
- «Фреймворк» для одного випадку
Чому це погано:
- Складність без користі
- YAGNI (You Ain't Gonna Need It — Вам це не знадобиться)
- Важче зрозуміти
Рефакторинги:
- Collapse Hierarchy
- Inline Class
- Remove Parameter
- Rename Method
Звʼязувачі (Couplers)
Запахи, що представляють надмірний звʼязок між класами.
Feature Envy (Заздрість до функцій)
Ознаки:
- Метод використовує більше даних іншого класу, ніж свого
- Багато викликів геттерів іншого обʼєкта
- Дані та поведінка розділені
Чому це погано:
- Неправильне розташування поведінки
- Погана інкапсуляція
- Важко супроводжувати
Рефакторинги:
- Move Method
- Move Field
- Extract Method (потім перемістити)
Приклад (до):
class Order {
getDiscountedPrice(customer) {
// Інтенсивно використовує дані customer
if (customer.loyaltyYears > 5) {
return this.price * customer.discountRate;
}
return this.price;
}
}Приклад (після):
class Customer {
getDiscountedPriceFor(price) {
if (this.loyaltyYears > 5) {
return price * this.discountRate;
}
return price;
}
}Inappropriate Intimacy (Недоречна інтимність)
Ознаки:
- Класи отримують доступ до приватних частин один одного
- Двосторонні посилання
- Підкласи знають занадто багато про батьків
Чому це погано:
- Високий звʼязок
- Зміни каскадуються
- Важко модифікувати один без іншого
Рефакторинги:
- Move Method
- Move Field
- Change Bidirectional to Unidirectional
- Extract Class
- Hide Delegate
Message Chains (Ланцюжки повідомлень)
Ознаки:
- Довгі ланцюжки викликів:
a.getB().getC().getD().getValue() - Клієнт залежить від структури навігації
- Код типу «потяг-катастрофа» (train wreck)
Чому це погано:
- Крихкий — будь-яка зміна ламає ланцюжок
- Порушує Закон Деметри
- Звʼязок зі структурою
Рефакторинги:
- Hide Delegate
- Extract Method
- Move Method
Приклад:
// Погано: Ланцюжок повідомлень
const managerName = employee.getDepartment().getManager().getName();
// Краще: Приховати делегування
const managerName = employee.getManagerName();Middle Man (Посередник)
Ознаки:
- Клас, що лише делегує іншому
- Половина методів — делегування
- Немає доданої цінності
Чому це погано:
- Непотрібна непрямість
- Накладні витрати обслуговування
- Заплутана архітектура
Рефакторинги:
- Remove Middle Man
- Inline Method
Посібник серйозності запахів
| Серйозність | Опис | Дія |
|---|---|---|
| Критичний | Блокує розробку, спричиняє помилки | Виправити негайно |
| Високий | Значний тягар обслуговування | Виправити в поточному спринті |
| Середній | Помітний, але керований | Запланувати на найближче майбутнє |
| Низький | Незначна незручність | Виправляти при нагоді |
Контрольний список швидкого виявлення
Використовуйте цей список при скануванні коду:
- Є методи > 30 рядків?
- Є класи > 300 рядків?
- Є методи з > 4 параметрами?
- Є дубльовані блоки коду?
- Є switch/case за кодами типів?
- Є невикористаний код?
- Є методи, що інтенсивно використовують дані іншого класу?
- Є довгі ланцюжки викликів?
- Є коментарі, що пояснюють «що», а не «чому»?
- Є примітиви, що мають бути обʼєктами?
Додаткове читання
- Fowler, M. (2018). Refactoring: Improving the Design of Existing Code (2nd ed.)
- Kerievsky, J. (2004). Refactoring to Patterns
- Feathers, M. (2004). Working Effectively with Legacy Code

