# Каталог запахів коду Комплексний довідник запахів коду на основі книги Мартіна Фаулера *Refactoring* (2-ге видання). Запахи коду — це симптоми глибших проблем; вони вказують на те, що щось може бути не так з дизайном вашого коду. > «Запах коду — це поверхнева ознака, яка зазвичай відповідає глибшій проблемі в системі.» — Мартін Фаулер --- ## Роздуті елементи (Bloaters) Запахи коду, що представляють щось, що розрослося занадто великим для ефективної роботи. ### Long Method (Довгий метод) **Ознаки:** - Метод перевищує 30-50 рядків - Потрібно прокручувати, щоб побачити весь метод - Кілька рівнів вкладеності - Коментарі пояснюють, що роблять секції **Чому це погано:** - Важко зрозуміти - Складно тестувати ізольовано - Зміни мають непередбачувані наслідки - Дубльована логіка ховається всередині **Рефакторинги:** - Extract Method - Replace Temp with Query - Introduce Parameter Object - Replace Method with Method Object - Decompose Conditional **Приклад (до):** ```javascript 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 } ``` **Приклад (після):** ```javascript 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 Кількість полів > 10 ``` --- ### Primitive Obsession (Одержимість примітивами) **Ознаки:** - Використання примітивів для доменних концепцій (string для email, int для грошей) - Масиви примітивів замість обʼєктів - Рядкові константи для кодів типів - Магічні числа/рядки **Чому це погано:** - Немає валідації на рівні типу - Логіка розкидана по кодовій базі - Легко передати неправильні значення - Відсутні доменні концепції **Рефакторинги:** - Replace Primitive with Object - Replace Type Code with Class - Replace Type Code with Subclasses - Replace Type Code with State/Strategy **Приклад (до):** ```javascript const user = { email: 'john@example.com', // Просто рядок phone: '1234567890', // Просто рядок status: 'active', // Магічний рядок balance: 10050 // Копійки як ціле число }; ``` **Приклад (після):** ```javascript 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 **Приклад (до):** ```javascript function createUser(firstName, lastName, email, phone, street, city, state, zip, isAdmin, isActive, createdBy) { // ... } ``` **Приклад (після):** ```javascript 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 **Приклад:** ```javascript // Група даних: координати (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 **Приклад (до):** ```javascript 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; } } ``` **Приклад (після):** ```javascript 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 погані коментарі:** ```javascript // ПОГАНО: Пояснює що // Цикл по користувачах і перевірка чи активні 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 **Виявлення:** ```bash # Шукати невикористані експорти # Шукати невикористані функції # Попередження 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 (потім перемістити) **Приклад (до):** ```javascript class Order { getDiscountedPrice(customer) { // Інтенсивно використовує дані customer if (customer.loyaltyYears > 5) { return this.price * customer.discountRate; } return this.price; } } ``` **Приклад (після):** ```javascript 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 **Приклад:** ```javascript // Погано: Ланцюжок повідомлень 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*