Запахи кода и как их рефакторить
Практика3 минРедакция Innovialabs
Коротко
Запах кода — это сигнал, что структура мешает изменениям; лечится он маленькими шагами рефакторинга, каждый из которых защищён тестами.
Что такое запах кода и когда его лечить
Запах кода — это не баг, а признак того, что код трудно читать и менять. Программа работает, но каждая правка занимает больше времени и чаще ломает соседние места.
Рефакторинг — изменение структуры без изменения поведения. Главное правило: сначала тесты, потом правка. Если поведение не зафиксировано тестами, вы не узнаете, что что-то сломали.
Лечить запах стоит, когда вы всё равно трогаете этот код: добавляете фичу или чините баг. Переписывать «на всякий случай» модуль, который никто не меняет, обычно невыгодно.
Порядок безопасного рефакторинга
- Напишите или найдите тесты, которые покрывают текущее поведение.
- Сделайте одно маленькое изменение.
- Запустите тесты.
- Зафиксируйте коммит.
- Повторите.
Маленькие коммиты легко откатить и легко проверить на ревью. Не смешивайте рефакторинг и новую функциональность в одном коммите.
Каталог запахов и лекарство
| Запах | Признак | Шаг рефакторинга |
|---|---|---|
| Длинный метод | Функция не помещается на экран, внутри комментарии-разделители | Extract Method |
| Дублирование | Одинаковый блок в нескольких местах | Вынести общую функцию |
| God object | Класс знает и делает всё | Extract Class по ответственности |
| Магические числа | if (status === 3) | Именованная константа или enum |
| Feature envy | Метод больше работает с чужими данными, чем со своими | Move Method к данным |
Длинный метод
Комментарии вроде // валидация, // расчёт, // сохранение — готовые имена для новых функций. Вынесите каждый блок в отдельный метод с понятным названием. Основная функция превратится в короткий сценарий из вызовов.
Дублирование логики
Прежде чем объединять, убедитесь, что код одинаков по смыслу, а не только по виду. Если два блока совпадают случайно и будут меняться по разным причинам, объединение создаст ложную связь. Если же это одно бизнес-правило — выносите в одну функцию.
God object
Признаки: сотни строк, десятки зависимостей, название вроде Manager или Helper. Сгруппируйте поля и методы по ответственности и вынесите каждую группу в отдельный класс. Исходный класс поначалу может просто делегировать вызовы — так внешний код не придётся менять сразу.
Магические числа и строки
// было
if (order.status === 3) refund(order);
// стало
const OrderStatus = { Paid: 1, Shipped: 2, Cancelled: 3 } as const;
if (order.status === OrderStatus.Cancelled) refund(order);
Имя объясняет смысл и даёт одно место для изменения.
Feature envy
Если метод класса Invoice постоянно читает поля Customer, чтобы посчитать скидку, логика скидки, скорее всего, принадлежит Customer. Перенесите метод туда, где живут данные.
Частые ошибки
- Рефакторинг без тестов. Начните хотя бы с характеризационных тестов, фиксирующих текущий вывод.
- Большой взрыв. Переписать модуль целиком за одну ветку — рискованно и тяжело ревьюить.
- Абстракции на будущее. Не выносите интерфейс, пока нет второго реального использования.
- Ручное переименование. Используйте автоматические рефакторинги IDE — они надёжнее поиска и замены.