Каждый разработчик рано или поздно встречается с кодом, который нужно переделать. Не потому что он не работает, а потому что работает плохо — медленно, неудобно, опасно. Вот это и есть рефакторинг. Но если просто взять и переписать всё подряд, можно легко сломать рабочую систему и потратить месяцы на поиск новых багов.
Я расскажу, как организовать программу рефакторинга так, чтобы она реально улучшала код, а не превращалась в бесконечный проект, который никто не финишит.
Что такое рефакторинг и почему он нужен
Рефакторинг — это переделка кода без изменения его функциональности. Код делает то же самое, но лучше: быстрее, понятнее, проще тестировать. Главное правило: поведение программы не меняется.
По-хорошему, рефакторинг должен быть частью ежедневной работы. Заметил неудачное имя переменной — переименовал. Функция выросла на 200 строк — разбил на несколько. Но это микро-рефакторинг. Когда же нужна целая программа?
Когда проблемы уже серьёзные:
- Добавление новых фич занимает в два раза дольше, чем раньше
- Code review превращается в пытку — одни сложные зависимости
- Половина багов появляется в местах кода, которые вообще не трогали
- Новые разработчики не разбираются в коде месяцами
- Тесты либо отсутствуют, либо падают без видимых причин
На одном проекте в Яндексе у нас была монолитная система управления платежами. Функция обработки платежа росла годами. В итоге это был класс на 1500 строк с 30 параметрами в конструкторе и переменными вроде status_tmp_2. Разработчики боялись что-то менять. Мы потратили три спринта на её разбор, но потом скорость разработки выросла на 40%.
Когда начинать программу рефакторинга
Главный вопрос: когда имеет смысл выделить ресурсы на рефакторинг?
Если время, которое разработчики тратят на понимание и исправление существующего кода, больше, чем на написание нового — пора. Некоторые команды считают это правилом 80/20: если более 80% времени уходит на поддержку, а не на развитие — это признак серьёзных проблем.
Ещё один сигнал — скорость появления новых багов растёт, хотя вы не меняете требования. Это значит, что система стала настолько запутанной, что никто не может в ней разобраться до конца.
Честно? Большинство команд начинают рефакторинг слишком поздно. Когда уже критично. Лучше заниматься этим постепенно.
Как организовать программу рефакторинга
Вот здесь главное — не превратить это в вечный проект.
Определи границы
Не рефакторь всё сразу. Выбери конкретный модуль, сервис или даже файл. На одном проекте мы решили переделать систему логирования. Это была одна папка с пятью файлами. Оценили на два спринта — в итоге потребовалось два с половиной. Управляемо.
Выделите процент времени
Не берите весь спринт только на рефакторинг. Выделите 30-40% ёмкости команды. Остальное — обычные фичи и баги. Почему? Потому что рефакторинг без баланса убивает мотивацию. Разработчик хочет видеть результаты работы в продакшене, а не только в коде.
Напиши план
Звучит очевидно, но мало кто это делает. План может быть простым:
Неделя 1: Анализ, покрытие тестами текущего кода
Неделя 2: Рефакторинг модуля A
Неделя 3: Рефакторинг модуля B
Неделя 4: Интеграция, тестирование, правки
Главное — чтобы был конец. Если план бесконечный, его забросят.
Code smells — сигналы того, что нужен рефакторинг
Code smell — это не баг, это вонь. Код работает, но пахнет неправильно. Вот самые частые:
Длинные функции
Функция на 100+ строк — это красный флаг. Её сложно тестировать, понять, что она делает.
Было:
def process_order(order_id, user_id, payment_method):
# 50 строк получения данных заказа
order = db.get_order(order_id)
user = db.get_user(user_id)
# 30 строк валидации
if not order:
raise Exception("Order not found")
if not user:
raise Exception("User not found")
# ... ещё 20 проверок
# 40 строк обработки платежа
if payment_method == "card":
# обработка карты
elif payment_method == "wallet":
# обработка кошелька
# ... ещё 5 методов
# 50 строк логирования и отправки уведомлений
log("Order processed")
send_email(user.email)
# ...
return result
Стало:
def process_order(order_id, user_id, payment_method):
order = db.get_order(order_id)
user = db.get_user(user_id)
_validate_order_and_user(order, user)
payment_result = _process_payment(order, payment_method)
_notify_user(user, order, payment_result)
return payment_result
def _validate_order_and_user(order, user):
if not order or not user:
raise ValueError("Invalid order or user")
# прочие проверки
def _process_payment(order, payment_method):
processors = {
"card": CardProcessor(),
"wallet": WalletProcessor(),
}
return processors[payment_method].process(order)
def _notify_user(user, order, result):
log(f"Order {order.id} processed")
send_email(user.email, order)
Разница огромная. Вторая версия — это история. Каждая функция делает одно.
Дублирование кода
Если один и тот же кусок повторяется в трёх местах — это не совпадение, это проблема.
Было:
# Файл users.py
def validate_email(email):
if '@' not in email:
raise ValueError("Invalid email")
if len(email) > 254:
raise ValueError("Email too long")
return email
# Файл orders.py
def validate_email(email):
if '@' not in email:
raise ValueError("Invalid email")
if len(email) > 254:
raise ValueError("Email too long")
return email
Стало:
# Файл validators.py
def validate_email(email):
if '@' not in email:
raise ValueError("Invalid email")
if len(email) > 254:
raise ValueError("Email too long")
return email
# Используем везде
from validators import validate_email
Просто. Эффективно.
Длинные списки параметров
Функция с восьмью параметрами — это сигнал к тому, что её нужно разделить или передавать объект.
# Было
def create_user(name, email, phone, address, city, country, zip_code, is_premium):
pass
# Стало
class UserData:
def __init__(self, name, email, phone, address, city, country, zip_code, is_premium):
self.name = name
self.email = email
# ...
def create_user(user_data: UserData):
pass
Магические числа и строки
Это когда в коде валяются константы без объяснений:
# Было
if user.age > 18: # Что это? Где это использовать?
grant_access()
# Стало
MIN_AGE_FOR_ACCESS = 18
if user.age > MIN_AGE_FOR_ACCESS:
grant_access()
Как не сломать всё при рефакторинге
Вот это самое важное. Как переписать код и спать спокойно?
Сначала тесты, потом код
Прежде чем начинать рефакторинг, покрой текущий код тестами. Это подушка безопасности. Если ты что-то сломаешь, тесты это покажут.
# Проверь покрытие перед рефакторингом
pytest --cov=my_module
Если покрытие менее 60% — не трогай. Сначала напиши тесты.
Маленькие шаги
Не переписывай сразу весь класс. Переделай одну функцию, прогони тесты, залей в мастер. Потом следующую.
На практике это выглядит так:
git checkout -b refactor/payment-processing
# Переделаешь _validate_payment()
pytest
git commit -m "refactor: split payment validation into separate function"
git push
# Создаёшь PR, коллеги смотрят
# После одобрения мерджишь
git checkout main
git merge refactor/payment-processing
Используй старый и новый код параллельно
Если это возможно, запусти новый код рядом со старым, но не используй результат:
def process_order(order_id):
# Старый код
result_old = _process_order_old(order_id)
# Новый код (параллельно, результат не используем)
try:
result_new = _process_order_new(order_id)
# Логируем разницу для отладки
if result_old != result_new:
log_warning(f"Results differ: {result_old} vs {result_new}")
except Exception as e:
log_error(f"New implementation failed: {e}")
# Возвращаем результат старого кода
return result_old
Потом, когда уверен, что новый код работает, переключаешься на него.
Code review как защита
Когда сдаёшь PR с рефакторингом, попроси коллегу проверить. Он может заметить то, что не видишь ты. Хороший код review — это не личное, это забота о качестве.
Инструменты, которые помогают
Автоматическое тестирование
Без CI/CD рефакторинг — рулетка. Каждый коммит должен гонять тесты автоматически.
# .gitlab-ci.yml
test:
stage: test
script:
- pytest --cov=src
- flake8 src
- mypy src
Линтеры и форматеры
Они спасают от глупых ошибок и стиля:
# Автоматический формат кода
black src/
isort src/
# Проверка стиля
flake8 src/
pylint src/
Статический анализ
Инструменты вроде SonarQube, Pylint, ESLint находят проблемы, которые пропустит человек. Но будь аккуратнее с их рекомендациями — не все они имеют смысл для твоего проекта.
Если ты используешь GitHub или GitLab, стоит посмотреть на автоматический code review. Есть инструменты, которые смотрят каждый PR и находят проблемы до того, как код попадёт в мастер. Например, Distiq интегрируется за пару минут и ловит баги, уязвимости и code smells прямо в PR.
Как измерить успех рефакторинга
Хорошо бы понять, помогло ли это вообще.
Метрики кода
- Циклическая сложность упала с 25 до 8? Хорошо.
- Среднее количество параметров функции упало с 6 до 3? Хорошо.
- Покрытие тестами выросло с 40% до 80%? Отлично.
Скорость разработки
Самая важная метрика — сколько времени занимает добавить новую фичу?
До рефакторинга: новая фича за 5 дней
После рефакторинга: новая фича за 2 дня
Вот это результат.
Количество багов
Если рефакторинг удачный, количество багов в этом модуле должно упасть. Не потому что ты стал лучше кодить, а потому что код стал понятнее.
Финальные советы
Рефакторинг — это не одноразовая работа. Это привычка. Лучше заниматься им понемногу каждый день, чем потом резать вены, переписывая половину проекта.
Если в вашей команде нет культуры рефакторинга, начни с малого. На каждом PR улучшай немного. Через месяц-два люди заметят, что код стал лучше, и это станет нормой.
И да, если у тебя есть большой проект, не забывай про автоматизацию проверок. Вручную ловить code smells — это потеря времени. Инструменты вроде линтеров и анализаторов спасают часы. Если добавить сюда автоматический code review в каждый PR, можно ловить проблемы прямо в процессе разработки, до мержа в основную ветку. Это делает рефакторинг проще и безопаснее.
