Рефакторинг: полный каталог приёмов Мартина Фаулера
Рефакторинг — это контролируемый процесс преобразования кода, улучшающий его внутреннюю структуру без изменения внешнего поведения. Термин введён Мартином Фаулером в книге «Refactoring: Improving the Design of Existing Code» (1999, 2-е издание — 2018). Рефакторинг — не разовая акция, а дисциплина: серия маленьких, безопасных шагов, каждый из которых сохраняет работоспособность кода. В этой статье — полный каталог приёмов из второго издания книги, сгруппированных по шести категориям, с примерами, схемами и практическими правилами «когда — да, когда — нет».
Когда делать рефакторинг
✅ Делайте:
- Перед добавлением новой функции — приведите код в порядок, чтобы новая функция легла на чистую основу
- Сразу после написания кода — когда всё свежо в голове
- При code review — когда коллега указал на проблемное место
- Когда видите «запах кода» (code smell) — дублирование, длинный метод, большой класс, длинный список параметров
❌ Не делайте:
- Когда код не покрыт тестами — рефакторинг без тестов = игра в русскую рулетку
- За день до релиза — риск внесения багов без времени на откат
- Когда код скоро будет выброшен (переписан с нуля)
- Когда «просто хочется красоты», а код и так работает, понятен и расширяем
- На продакшене без резервной копии
Обзор категорий
1. Composing Methods — композиция методов
Приёмы для работы с границами функций: выделение, перенос, объединение кода.
Extract Function (Извлечение функции)
Обратный приём: Inline Function
Самый часто используемый рефакторинг. Фрагмент кода, который можно логически сгруппировать, выносится в отдельную функцию с понятным именем.
До:
function printOwing(invoice) {
printBanner();
let outstanding = calculateOutstanding();
//print details
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
}
После:
function printOwing(invoice) {
printBanner();
let outstanding = calculateOutstanding();
printDetails(outstanding);
function printDetails(outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
}
}
✅ Когда применять: длинная функция (>10-15 строк), код с комментарием-заголовком, повторяющийся фрагмент. ❌ Не применять: когда фрагмент жёстко привязан к локальному контексту (много параметров-локальных переменных), когда функция уже короткая — избыточное дробление ухудшает читаемость.
Inline Function (Встраивание функции)
Обратный приём: Extract Function
Убирает лишний уровень косвенности: тело функции вставляется на место вызова, а сама функция удаляется.
✅ Когда применять: функция тривиальна (одна-две строки) и не лучше своего тела, вызовов мало, косвенность не оправдана. ❌ Не применять: функция используется во многих местах, функция инкапсулирует нетривиальную логику, функция — часть публичного API.
Extract Variable (Извлечение переменной)
Сложное выражение заменяется переменной с осмысленным именем.
✅ Когда применять: выражение трудно читается, одно и то же подвыражение используется несколько раз. ❌ Не применять: в простых выражениях, где переменная не добавляет ясности.
Slide Statements (Скольжение операторов)
Связанные операторы перемещаются ближе друг к другу — чтобы было очевидно, что они работают вместе.
✅ Когда применять: родственные строки разбросаны по функции. ❌ Не применять: когда порядок важен и есть побочные эффекты.
Split Phase (Разделение фазы)
Код, выполняющий несколько разных задач последовательно, разделяется на отдельные фазы.
✅ Когда применять: функция делает две разные вещи, каждую из которых нужно тестировать или переиспользовать отдельно. ❌ Не применять: фазы тесно переплетены, разделение создаст больше сложности, чем уберёт.
2. Moving Features — перемещение кода между модулями
Приёмы для перемещения функций, полей и операторов в правильные места.
Move Function (Перемещение функции)
Функция переносится в тот класс/модуль, с которым она взаимодействует больше всего.
До:
class Account {
get overdraftCharge() { /* ... */ }
}
После:
class AccountType {
get overdraftCharge() { /* ... */ }
}
✅ Когда применять: функция чаще обращается к полям другого класса, чем своего. ❌ Не применять: функция равномерно использует оба класса — возможно, нужен третий.
Move Field (Перемещение поля)
Поле перемещается из одного класса в другой, более подходящий.
✅ Когда применять: поле чаще используется методами другого класса. ❌ Не применять: поле используется обоими классами одинаково часто.
Replace Loop with Pipeline (Замена цикла конвейером)
Цикл заменяется цепочкой функций высшего порядка (map, filter, reduce).
✅ Когда применять: цикл выполняет несколько разных операций над коллекцией — pipeline разделит их. ❌ Не применять: одна простая операция — pipeline избыточен; производительность критична (pipeline создаёт промежуточные массивы).
3. Organizing Data — организация данных
Приёмы для упрощения работы с переменными, типами и структурами данных.
Split Variable (Разделение переменной)
Переменная, которая используется для нескольких разных целей, разделяется на отдельные переменные под каждую цель.
✅ Когда применять: одна переменная переиспользуется для разных значений в течение функции. ❌ Не применять: переменная аккумулирует одно значение (как в циклах).
Rename Variable / Rename Field (Переименование)
Переменная или поле получает имя, которое точно отражает её назначение.
✅ Когда применять: всегда, когда имя не очевидно при первом чтении. ❌ Не применять: переименование публичного API без обновления всех клиентов.
Replace Primitive with Object (Замена примитива объектом)
Примитивный тип данных (строка, число) заменяется классом, инкапсулирующим поведение, связанное с этим значением.
✅ Когда применять: с примитивом связано поведение (валидация, форматирование), которое размазано по коду. ❌ Не применять: простые значения без поведения — избыточное ООП.
Replace Temp with Query (Замена временной переменной запросом)
Временная переменная, хранящая результат выражения, заменяется вызовом функции.
✅ Когда применять: выражение используется один раз, и его вынос в функцию улучшает читаемость. ❌ Не применять: вычисление дорогое и вызывается многократно — сохраните в переменную.
Encapsulate Variable (Инкапсуляция переменной)
Прямой доступ к переменной заменяется геттерами и сеттерами.
✅ Когда применять: переменная используется в разных модулях, и нужно контролировать доступ к ней. ❌ Не применять: локальная переменная внутри функции.
4. Simplifying Conditional Logic — упрощение условной логики
Приёмы для борьбы с запутанными условиями.
Decompose Conditional (Декомпозиция условия)
Сложное условие и каждая его ветка выносятся в отдельные функции с осмысленными именами.
Было: if (date.before(SUMMER_START) || date.after(SUMMER_END))
Стало: if (isSummer(date))
if (notSummer(date))
✅ Когда применять: условие требует комментария или мысленного усилия для понимания.
❌ Не применять: тривиальные условия — if (x > 0) не нуждается в функции.
Consolidate Conditional Expression (Консолидация условий)
Несколько условий, ведущих к одному результату, объединяются в одно.
✅ Когда применять: несколько if подряд возвращают/делают одно и то же.
❌ Не применять: условия логически независимы и их объединение усложнит понимание.
Replace Nested Conditional with Guard Clauses (Замена вложенных условий охранными операторами)
✅ Когда применять: глубокая вложенность if-else; хочется видеть сначала проверки-барьеры, а потом основную логику. ❌ Не применять: все ветки равнозначны по важности — guard clauses не нужны.
Replace Conditional with Polymorphism (Замена условного оператора полиморфизмом)
До:
switch (bird.type) {
case 'EuropeanSwallow':
return "average";
case 'AfricanSwallow':
return (bird.numberOfCoconuts > 2) ? "tired" : "average";
case 'NorwegianBlueParrot':
return (bird.voltage > 100) ? "scorched" : "beautiful";
default:
return "unknown";
}
После:
class EuropeanSwallow {
get plumage() { return "average"; }
}
class AfricanSwallow {
get plumage() {
return (this.numberOfCoconuts > 2) ? "tired" : "average";
}
}
class NorwegianBlueParrot {
get plumage() {
return (this.voltage > 100) ? "scorched" : "beautiful";
}
}
✅ Когда применять: switch/if-else по типу объекта повторяется в нескольких местах; типов много и будут добавляться новые. ❌ Не применять: switch используется один раз и вряд ли повторится; типов всего 2-3 и они стабильны.
5. Refactoring APIs — рефакторинг интерфейсов
Приёмы для улучшения границ между модулями.
Separate Query from Modifier (Разделение запроса и модификации)
Функция, которая и возвращает значение, и меняет состояние, разделяется на две: одна возвращает, другая меняет.
✅ Когда применять: функция делает две вещи — возвращает значение и имеет побочный эффект. ❌ Не применять: разделение невозможно без дублирования дорогой логики.
Remove Flag Argument (Удаление флагового аргумента)
Булев или строковый параметр, управляющий поведением функции, заменяется отдельными функциями.
Было: bookEvent(‘conference’, true) // true = premium
Стало: bookPremiumEvent('conference')
✅ Когда применять: флаг радикально меняет поведение функции. ❌ Не применять: флаг управляет незначительной вариацией внутри одной и той же логики.
Parameterize Function (Параметризация функции)
Две функции с похожей логикой заменяются одной с параметром.
✅ Когда применять: дублирование логики с разными константами. ❌ Не применять: функции различаются не только константами, но и структурой.
Preserve Whole Object (Передача целого объекта)
Вместо нескольких полей объекта в функцию передаётся сам объект.
✅ Когда применять: функция берёт несколько полей из одного объекта; если у объекта изменятся поля, сигнатуру функции менять не придётся. ❌ Не применять: объект большой, а функции нужны 1-2 поля — передача всего объекта создаёт лишнюю зависимость.
6. Dealing with Inheritance — работа с наследованием
Приёмы для реорганизации иерархии классов.
Pull Up Method / Pull Up Field (Подъём метода/поля)
Метод или поле, дублирующееся в подклассах, поднимается в суперкласс.
✅ Когда применять: идентичный код в двух и более подклассах. ❌ Не применять: копия не полностью идентична, нужна адаптация.
Push Down Method / Push Down Field (Спуск метода/поля)
Метод или поле, используемое только частью подклассов, спускается в них.
✅ Когда применять: поведение в суперклассе нужно не всем подклассам. ❌ Не применять: метод используется большинством подклассов.
Extract Superclass (Извлечение суперкласса)
Из нескольких классов с общей функциональностью выделяется общий родитель.
✅ Когда применять: два класса имеют много общего поведения. ❌ Не применять: классы связаны концептуально слабо; лучше использовать композицию.
Replace Subclass with Delegate (Замена подкласса делегатом)
Подкласс заменяется отдельным объектом-делегатом.
✅ Когда применять: иерархия статична, а поведение должно меняться динамически; подкласс переопределяет только часть поведения. ❌ Не применять: подкласс радикально отличается от родителя по структуре.
Replace Superclass with Delegate (Замена суперкласса делегатом)
Наследование заменяется композицией — суперкласс становится полем-делегатом.
✅ Когда применять: суперкласс не является логическим «родителем»; наследование заставляет тащить ненужные методы. ❌ Не применять: отношение действительно «is-a», и наследование адекватно его выражает.
Полный алфавитный указатель приёмов
Антипаттерны рефакторинга
То, чего следует избегать:
- Big Bang Refactoring — «сейчас я всё перепишу за одну ночь». Результат: код сломан, тестов нет, откатить нельзя.
- Refactoring without tests — рефакторинг без покрытия тестами. Вы не узнаете, что сломали, пока не упадёт продакшен.
- Over-refactoring — рефакторинг ради рефакторинга. Код уже хорош, а вы продолжаете «улучшать».
- Refactoring + feature в одном PR — смешивание рефакторинга и новой функциональности в одном коммите. При code review невозможно понять, где изменения структуры, а где — поведения.
Рекомендуемые книги
| Книга | Автор | О чём |
|---|---|---|
| Refactoring: Improving the Design of Existing Code (2nd ed.) | Мартин Фаулер | Первоисточник: каталог приёмов с примерами на JavaScript |
| Clean Code | Роберт Мартин | Принципы чистого кода — дополнение к рефакторингу |
| Working Effectively with Legacy Code | Майкл Физерс | Рефакторинг легаси-кода без тестов |
| Refactoring to Patterns | Джошуа Кериевски | Как применять рефакторинг для внедрения паттернов проектирования |