Инженерия ПО
Рефакторинг: когда и как
В 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