mirror of
https://github.com/luongnv89/claude-howto.git
synced 2026-06-05 22:36:34 +02:00
feat(uk): translate refactor references (code-smells + catalog)
03-skills/refactor/references/code-smells.md (669 lines) 03-skills/refactor/references/refactoring-catalog.md (1023 lines) P3 complete. Remaining: P4 root docs (5 files). Ref: luongnv89/claude-howto#63
This commit is contained in:
@@ -0,0 +1,669 @@
|
||||
# Каталог запахів коду
|
||||
|
||||
Комплексний довідник запахів коду на основі книги Мартіна Фаулера *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*
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user