Кладовочка

О разном, чтобы было под рукой онлайн

Рефакторинг: полный каталог приёмов Мартина Фаулера

Рефакторинг — это контролируемый процесс преобразования кода, улучшающий его внутреннюю структуру без изменения внешнего поведения. Термин введён Мартином Фаулером в книге «Refactoring: Improving the Design of Existing Code» (1999, 2-е издание — 2018). Рефакторинг — не разовая акция, а дисциплина: серия маленьких, безопасных шагов, каждый из которых сохраняет работоспособность кода. В этой статье — полный каталог приёмов из второго издания книги, сгруппированных по шести категориям, с примерами, схемами и практическими правилами «когда — да, когда — нет».

graph TD A[Написал код] --> B{Работает?} B -->|Нет| C[Исправь баг] B -->|Да| D{Понятен ли код?} D -->|Да| E{Дублирование?} D -->|Нет| F[Рефакторинг: улучши читаемость] E -->|Да| G[Рефакторинг: устрани дублирование] E -->|Нет| H{Легко ли расширять?} H -->|Да| I[Готово ✅] H -->|Нет| J[Рефакторинг: улучши структуру] F --> D G --> D J --> D

Когда делать рефакторинг

✅ Делайте:

  • Перед добавлением новой функции — приведите код в порядок, чтобы новая функция легла на чистую основу
  • Сразу после написания кода — когда всё свежо в голове
  • При code review — когда коллега указал на проблемное место
  • Когда видите «запах кода» (code smell) — дублирование, длинный метод, большой класс, длинный список параметров

❌ Не делайте:

  • Когда код не покрыт тестами — рефакторинг без тестов = игра в русскую рулетку
  • За день до релиза — риск внесения багов без времени на откат
  • Когда код скоро будет выброшен (переписан с нуля)
  • Когда «просто хочется красоты», а код и так работает, понятен и расширяем
  • На продакшене без резервной копии

Обзор категорий

mindmap root((Рефакторинг)) Composing Methods Extract Function Inline Function Extract Variable Inline Variable Split Phase Slide Statements Moving Features Move Function Move Field Move Statements into Function Move Statements to Callers Replace Loop with Pipeline Organizing Data Split Variable Rename Variable Replace Primitive with Object Replace Temp with Query Encapsulate Variable Simplifying Conditionals Decompose Conditional Consolidate Conditional Replace Nested Conditional with Guard Clauses Replace Conditional with Polymorphism Refactoring APIs Separate Query from Modifier Parameterize Function Replace Parameter with Query Remove Flag Argument Preserve Whole Object Dealing with Inheritance Pull Up Method Push Down Method Extract Superclass Replace Subclass with Delegate Replace Superclass with Delegate

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 (Разделение фазы)

Код, выполняющий несколько разных задач последовательно, разделяется на отдельные фазы.

flowchart LR A[Чтение + Обработка + Запись] --> B[Чтение] --> C[Обработка] --> D[Запись]

✅ Когда применять: функция делает две разные вещи, каждую из которых нужно тестировать или переиспользовать отдельно. ❌ Не применять: фазы тесно переплетены, разделение создаст больше сложности, чем уберёт.


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 (Замена вложенных условий охранными операторами)

flowchart TD subgraph Было A[if условие1] --> B[if условие2] B --> C[if условие3] C --> D[основная логика] end subgraph Стало E[if !условие1 → return] --> F[if !условие2 → return] F --> G[if !условие3 → return] G --> H[основная логика] end

✅ Когда применять: глубокая вложенность 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 (Разделение запроса и модификации)

Функция, которая и возвращает значение, и меняет состояние, разделяется на две: одна возвращает, другая меняет.

flowchart LR A[getAndSet] --> B[getState] A --> C[setState]

✅ Когда применять: функция делает две вещи — возвращает значение и имеет побочный эффект. ❌ Не применять: разделение невозможно без дублирования дорогой логики.

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», и наследование адекватно его выражает.


Полный алфавитный указатель приёмов

Приём Категория Обратный приём
Change Function Declaration API —
Change Reference to Value Данные Change Value to Reference
Change Value to Reference Данные Change Reference to Value
Collapse Hierarchy Наследование —
Combine Functions into Class Композиция —
Combine Functions into Transform Композиция —
Consolidate Conditional Expression Условия —
Decompose Conditional Условия —
Encapsulate Collection Данные —
Encapsulate Record Данные —
Encapsulate Variable Данные —
Extract Class Композиция Inline Class
Extract Function Композиция Inline Function
Extract Superclass Наследование —
Extract Variable Композиция Inline Variable
Hide Delegate Перемещение Remove Middle Man
Inline Class Композиция Extract Class
Inline Function Композиция Extract Function
Inline Variable Композиция Extract Variable
Introduce Assertion API —
Introduce Parameter Object API —
Introduce Special Case API —
Move Field Перемещение —
Move Function Перемещение —
Move Statements into Function Перемещение —
Move Statements to Callers Перемещение —
Parameterize Function API —
Preserve Whole Object API —
Pull Up Constructor Body Наследование Push Down Method
Pull Up Field Наследование Push Down Field
Pull Up Method Наследование Push Down Method
Push Down Field Наследование Pull Up Field
Push Down Method Наследование Pull Up Method
Remove Dead Code API —
Remove Flag Argument API —
Remove Middle Man Перемещение Hide Delegate
Remove Setting Method Данные —
Remove Subclass Наследование —
Rename Field Данные —
Rename Variable Данные —
Replace Command with Function API Replace Function with Command
Replace Conditional with Polymorphism Условия —
Replace Constructor with Factory Function API —
Replace Derived Variable with Query Данные —
Replace Error Code with Exception API Replace Exception with Precheck
Replace Exception with Precheck API Replace Error Code with Exception
Replace Function with Command API Replace Command with Function
Replace Inline Code with Function Call Композиция —
Replace Loop with Pipeline Перемещение —
Replace Magic Literal Данные —
Replace Nested Conditional with Guard Clauses Условия —
Replace Parameter with Query API Replace Query with Parameter
Replace Primitive with Object Данные —
Replace Query with Parameter API Replace Parameter with Query
Replace Subclass with Delegate Наследование —
Replace Superclass with Delegate Наследование —
Replace Temp with Query Данные —
Replace Type Code with Subclasses Наследование —
Return Modified Value Перемещение —
Separate Query from Modifier API —
Slide Statements Композиция —
Split Loop Композиция Replace Loop with Pipeline
Split Phase Композиция —
Split Variable Данные —
Substitute Algorithm Композиция —

Антипаттерны рефакторинга

То, чего следует избегать:

  1. Big Bang Refactoring — «сейчас я всё перепишу за одну ночь». Результат: код сломан, тестов нет, откатить нельзя.
  2. Refactoring without tests — рефакторинг без покрытия тестами. Вы не узнаете, что сломали, пока не упадёт продакшен.
  3. Over-refactoring — рефакторинг ради рефакторинга. Код уже хорош, а вы продолжаете «улучшать».
  4. 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 Джошуа Кериевски Как применять рефакторинг для внедрения паттернов проектирования

Связанные статьи