Инженерия ПО

Рефакторинг: когда и как

В 1999 году Netscape решили переписать браузер с нуля. На 3 года Internet Explorer получил монополию. Когда новый Netscape 6 вышел - доля упала с 80% до 7%. Joel Spolsky назвал это "the single worst strategic mistake that any software company can make". Альтернативой был рефакторинг.

  • **Netscape 6 (2001)**: rewrite с нуля стоил 3 года и 73% рыночной доли
  • **Riot Games (2018)**: rewrite клиента League of Legends - 2 года, продукт едва выжил
  • **Riot Games (2020)**: миграция рендерера через рефакторинг - 6 месяцев, ноль downtime
  • **Spotify Engineering**: 20% rule - команды постоянно тратят пятую часть времени на технический долг
  • **Stripe API**: эволюция через рефакторинг с 2011 года - 13 лет без breaking change major-версии

Code smells: запахи кода

Code smell - это поверхностный признак более глубокой проблемы в коде. Smell не баг: код работает. Но он сигналит, что структура скоро перестанет выдерживать новые требования. Фаулер описал около 20 канонических smells, и каждый имеет рецепт рефакторинга.

**Топ-5 smells:** Long Method (>20 строк), Large Class (>200 строк, >7 полей), Long Parameter List (>4 параметров), Duplicated Code (одинаковые блоки в разных местах), Feature Envy (метод обращается к чужим данным чаще чем к своим).

Метод DeliveryService.calculatePrice() читает 8 полей класса Order и ни одного своего. Какой это smell?

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

Каждому smell соответствует один или несколько named refactorings - это типизированные преобразования с гарантией сохранения поведения. IDE (IntelliJ, ReSharper, Rider) умеют применять их автоматически, что критично снижает риск ошибки.

**Базовая шестёрка:** Extract Method (длинный код → отдельная функция), Inline Method (обратное), Rename (переименовать), Move Method/Field (перенести), Extract Class (выделить класс), Replace Conditional with Polymorphism (switch → классы наследники).

В классе 600 строк и 4 группы методов работают с непересекающимися полями. Какой рефакторинг подходит?

Безопасный рефакторинг

Главное правило: рефакторинг не должен менять поведение. Гарантировать это можно только тестами. Без покрытия рефакторинг становится переписыванием - и часто заканчивается регрессией в проде через две недели.

**Алгоритм Мартина Фаулера:** 1) Убедиться, что тесты зелёные. 2) Один маленький шаг рефакторинга. 3) Запустить тесты. 4) Коммит. 5) Повторить. Если на шаге 3 тесты красные - откатиться и разбить шаг ещё мельче.

**Antipattern:** "Большой рефакторинг" - две недели в feature branch, мердж в main, всё ломается. Правильно - mikado method или strangler fig: постепенная замена компонентов при работающей системе.

Когда рефакторинг безопаснее всего проводить?

Технический долг

Метафора Уорда Каннингема: компромисс с архитектурой работает как кредит. Можно взять в долг для быстрого релиза, но проценты в виде замедления разработки растут с каждой неделей. Если не платить - наступает банкротство и переписывание с нуля.

**Квадрант Фаулера:** Намеренный/Случайный × Безрассудный/Осмотрительный. Самый опасный - Случайный Безрассудный ("мы не знали, как правильно, и не задумывались"). Лучший - Намеренный Осмотрительный ("мы выкатим сейчас, отрефакторим в следующем спринте").

**Стратегии выплаты:** 20% rule (Spotify) - команды тратят 20% времени на долг. Refactoring-friday - один день в неделю на чистку. Boy Scout rule - каждая задача оставляет код чище. Антикризисная - dedicated tech debt sprint раз в квартал.

Рефакторинг = переписать модуль с нуля заново.

Рефакторинг - серия микрошагов, после каждого код собирается и проходит тесты. Переписывание (rewrite) меняет поведение и не имеет промежуточных рабочих состояний.

Riot Games в 2018 переписали клиент LoL с нуля - заняло 2 года и едва не убило проект. Они же позже мигрировали на новый рендеринг рефакторингом - 6 месяцев, ноль downtime. Промежуточные рабочие состояния - ключевое отличие, оно даёт возможность откатиться на любом шаге.

Команда жалуется: фичи делаются всё медленнее, тесты падают случайно, новый код страшно писать. Что происходит?

Ключевые идеи

  • Code smell - поверхностный признак глубокой проблемы. Топ-5: Long Method, Large Class, Long Parameter List, Duplicated Code, Feature Envy
  • Каждый smell имеет рецепт. Базовая шестёрка: Extract Method, Inline, Rename, Move, Extract Class, Replace Conditional with Polymorphism
  • Безопасный рефакторинг: маленькие шаги, зелёные тесты после каждого, коммит. Mikado method или strangler fig вместо big bang
  • Технический долг как кредит: проценты растут. Стратегии выплаты: 20% rule, Boy Scout rule, refactoring-friday. Игнорирование ведёт к банкротству

Связанные темы

Рефакторинг невозможен без страховки тестами и осмысленности через принципы качества кода:

  • Property-Based и Mutation Testing — тесты как страховка рефакторинга
  • Clean Code — следующий уровень после рефакторинга
  • Unit Testing — обязательное условие безопасного рефакторинга
  • SOLID принципы — критерии оценки результата рефакторинга

Вопросы для размышления

  • Назовите три code smell из вашего текущего проекта. Какой рефакторинг применили бы к каждому?
  • Когда последний раз технический долг блокировал фичу? Что нужно было выплатить в первую очередь?
  • Опишите безопасный рефакторинг 800-строчного класса без тестов. С чего начать?
  • Чем отличается рефакторинг от переписывания? Когда выбрать второе?

Связанные уроки

  • se-13 — Тесты страхуют рефакторинг от регрессий
  • se-15 — Рефакторинг открывает путь к паттернам проектирования
  • alg-01-big-o — Понимание сложности помогает оценить impact рефакторинга
  • ds-06-hash-intro — Replace условий хеш-таблицей - классический рефакторинг производительности
  • db-11-query-optimization — Query refactoring и code refactoring - одни принципы читаемости
  • comp-01-intro
Рефакторинг: когда и как

0

1

Войти