Рефакторинг7 мин чтения2026-03-06

Рефакторинг кода: как переделать старое и не сломать новое

Рефакторинг — это не про переписывание всего с нуля. Это про улучшение внутренней структуры кода без изменения его поведения. Звучит просто? На деле это одна из

Рефакторинг — это не про переписывание всего с нуля. Это про улучшение внутренней структуры кода без изменения его поведения. Звучит просто? На деле это одна из самых сложных операций в разработке. Я видел команды, которые превращали рефакторинг в многомесячный кошмар, потому что не знали, как к нему подойти правильно.

По-хорошему, рефакторинг нужно делать постоянно. Маленькими шагами. А не накапливать техдолг годами, а потом пытаться переделать всё в спринт. Но об этом позже.

Что вообще такое рефакторинг и зачем он нужен

Рефакторинг — это преобразование кода, которое улучшает его качество, но не меняет функциональность. Ключевое слово тут "не меняет". Если ты переделываешь логику — это не рефакторинг, это развитие фичи или баг-фикс.

Зачем нужен? Если честно, от нехватки рефакторинга страдают все проекты после года-двух разработки. Код накапливает:

Результат: разработчики тратят в два раза больше времени на понимание кода. Баги вылезают чаще. Новичков тяжелее онбордить. Скорость разработки падает линейно.

Рефакторинг замораживает это падение. Иногда даже разворачивает его вспять.

Code smells: как узнать, что пора рефакторить

"Code smell" — это не баг. Это признак, что с кодом что-то не так. Как запах из холодильника: может быть, там уже плесень, а может, просто скоро будет.

Вот самые частые:

Дублированный код (Duplicated Code)

Если один и тот же блок повторяется в трёх местах — это беда. На одном проекте я нашёл функцию валидации email-а, скопированную в восьми местах. Каждая слегка отличалась. Когда нужно было поправить регулярное выражение, пришлось менять в пяти местах (два раза забыли, потом на продакшене упали). Решение — извлечь в отдельную функцию.

# ДО: дублирование
def register_user(email, password):
    if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
        return False
    # ... регистрация

def update_user_email(user_id, email):
    if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
        return False
    # ... обновление

# ПОСЛЕ: извлекли в функцию
def is_valid_email(email):
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return re.match(pattern, email) is not None

def register_user(email, password):
    if not is_valid_email(email):
        return False
    # ...

def update_user_email(user_id, email):
    if not is_valid_email(email):
        return False
    # ...

Просто? Да. Но когда такого дублирования в проекте тысячи строк, это экономит часы отладки.

Длинные функции (Long Method)

Функция в 200 строк — это уже красный флаг. В 500 — это катастрофа. Я видел метод в старом коде, который парсил XML, валидировал данные, писал в базу, отправлял письмо и логировал результат. Всё в одной функции. Изменить хотя бы один шаг было страшно.

# ДО: монолит
def process_order(order_data):
    # Парсим данные (30 строк)
    items = json.loads(order_data)
    # Валидируем (40 строк)
    for item in items:
        if 'price' not in item:
            raise ValueError(...)
        if item['price'] < 0:
            raise ValueError(...)
    # Считаем итого (20 строк)
    total = sum(item['price'] * item['quantity'] for item in items)
    # Пишем в БД (35 строк)
    db.execute(...)
    # Отправляем письмо (25 строк)
    send_email(...)
    # Логируем (20 строк)
    logger.info(...)
    return total

# ПОСЛЕ: разбили на функции
def process_order(order_data):
    items = parse_order(order_data)
    validate_items(items)
    total = calculate_total(items)
    save_to_db(items, total)
    notify_user(items)
    log_transaction(items, total)
    return total

def parse_order(order_data):
    return json.loads(order_data)

def validate_items(items):
    for item in items:
        if 'price' not in item or item['price'] < 0:
            raise ValueError(f"Invalid item: {item}")

def calculate_total(items):
    return sum(item['price'] * item['quantity'] for item in items)

# ... остальные функции

Теперь каждую функцию легко тестировать, менять, понимать.

Неясные имена (Unclear Naming)

Переменные типа x, tmp, data, result ничего не говорят. Я потратил два часа на понимание кода, потому что переменная называлась m. Оказалось, это "maximum". Не "money", не "message", не "map". Просто "m".

# ДО
def calc(arr):
    m = 0
    for i in arr:
        if i > m:
            m = i
    return m

# ПОСЛЕ
def find_maximum_value(numbers):
    maximum = 0
    for number in numbers:
        if number > maximum:
            maximum = number
    return maximum

Или даже лучше:

def find_maximum_value(numbers):
    return max(numbers) if numbers else 0

Слишком много параметров (Long Parameter List)

Функция с десятью параметрами — это криптография. Каждый раз нужно помнить, в каком порядке их передавать.

# ДО: 8 параметров
def create_user(first_name, last_name, email, phone, address, 
                city, country, age):
    # ...

# ПОСЛЕ: используем класс или словарь
class UserData:
    def __init__(self, first_name, last_name, email, phone, 
                 address, city, country, age):
        self.first_name = first_name
        self.last_name = last_name
        # ... и т.д.

def create_user(user_data: UserData):
    # ...

Божественные классы (God Class)

Класс, который делает всё: парсит данные, работает с БД, отправляет письма, логирует. Обычно это сервис-класс на 2000 строк.

Решение — разбить на несколько классов по ответственности (SOLID, Single Responsibility Principle).

Методы рефакторинга: как переделать код правильно

Есть несколько стандартных техник. Мартин Фаулер описал их в книге "Refactoring", и за 25 лет они не устарели.

Extract Method (Извлечение метода)

Самая частая. Берёшь кусок кода, выделяешь его в отдельную функцию.

# ДО
def generate_report(data):
    # ... 50 строк подготовки данных
    # ... 30 строк форматирования
    # ... 20 строк записи в файл
    pass

# ПОСЛЕ
def generate_report(data):
    prepared_data = prepare_data(data)
    formatted_output = format_output(prepared_data)
    write_report(formatted_output)

def prepare_data(data):
    # 50 строк
    pass

def format_output(data):
    # 30 строк
    pass

def write_report(output):
    # 20 строк
    pass

Replace Magic Numbers with Named Constants

Числа без контекста — это яд. Что такое 86400? Что такое 0.15?

# ДО
def calculate_daily_earnings(hourly_rate, hours_worked):
    tax_rate = 0.15
    seconds_in_day = 86400
    daily_rate = hourly_rate * 8
    return (daily_rate * hours_worked - daily_rate * hours_worked * tax_rate)

# ПОСЛЕ
STANDARD_WORK_HOURS = 8
TAX_RATE = 0.15
SECONDS_IN_DAY = 86400

def calculate_daily_earnings(hourly_rate, hours_worked):
    daily_rate = hourly_rate * STANDARD_WORK_HOURS
    tax_amount = daily_rate * hours_worked * TAX_RATE
    return daily_rate * hours_worked - tax_amount

Rename Variable/Function

Банально, но работает. Переименовать usr в user, calc в calculate_salary — и код становится читаемым в два раза лучше.

Remove Dead Code

Старый код, на который никто не ссылается. Комментарий вида # это больше не используется, но оставляю на всякий случай. Удалить. Это не история, это мусор. У тебя есть Git.

# ДО
def get_user(user_id):
    # Старый способ (не используется)
    # user = db.query("SELECT * FROM users WHERE id = " + str(user_id))
    
    # Новый способ
    user = db.query(User).filter(User.id == user_id).first()
    return user

# ПОСЛЕ
def get_user(user_id):
    return db.query(User).filter(User.id == user_id).first()

Move Method/Field

Если метод работает с данными другого класса, может, ему там и место?

# ДО: метод в неправильном классе
class User:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary
    
    def calculate_tax(self):  # Это про деньги, не про пользователя
        return self.salary * 0.15

# ПОСЛЕ
class User:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

class PayrollCalculator:
    def calculate_tax(self, salary):
        return salary * 0.15

Как не сломать всё при рефакторинге

Вот где большинство команд спотыкается. Рефакторинг — это не просто переделка кода. Это переделка кода с гарантией, что ничего не сломалось.

Тесты — твой щит

Без тестов рефакторинг — это русская рулетка. Ты меняешь код, надеясь, что всё работает как раньше, но не знаешь точно. Результат: на продакшене падает.

Правило простое: перед рефакторингом напиши тесты на текущее поведение. Потом переделывай код. Если тесты зелёные — ты всё сделал правильно.

# Сначала напиши тест
def test_calculate_total():
    items = [
        {'price': 100, 'quantity': 2},
        {'price': 50, 'quantity': 3}
    ]
    assert calculate_total(items) == 350

# Потом переделывай код, тест гарантирует результат
def calculate_total(items):
    return sum(item['price'] * item['quantity'] for item in items)

Маленькие шаги, частые коммиты

Не меняй сразу 500 строк. Меняй 20-50 строк, коммитишь, пушишь, видишь результат в CI. Если что-то сломалось, откатиться легко.

git commit -m "Refactor: extract calculate_total function"
git push
# CI запустится, ты увидишь результат

Code review для рефакторинга

Если честно, большинство команд пропускают код-ревью для рефакторинга. "Ну это же не фича, что там смотреть". Ошибка. Второй глаз часто видит, что ты что-то упустил.

На проекте в Яндексе мы рефакторили парсер JSON. Я переделал функцию, думал, что всё окей. Ревьюер нашёл edge case с кодировкой UTF-8. Хорошо, что нашёл до продакшена.

Автоматизация проверок

Статические анализаторы типа pylint, flake8, eslint ловят часть проблем автоматически. Не полагайся на них, но используй. Это экономит время на code review.

# Python
pylint my_module.py
flake8 my_module.py

# JavaScript
eslint src/

# Java
mvn checkstyle:check

Профилирование перед и после

Если рефакторинг касается производительности — меряй. До и после. Иногда "красивый" код работает медленнее.

import time

# ДО
start = time.time()
result_old = old_function(large_data)
print(f"Old: {time.time() - start:.4f}s")

# ПОСЛЕ
start = time.time()
result_new = new_function(large_data)
print(f"New: {time.time() - start:.4f}s")

assert result_old == result_new  # Проверяем результат

Когда р

Попробуйте Distiq для автоматического code review

AI-бот анализирует каждый MR/PR и оставляет комментарии с замечаниями. Интеграция за 2 минуты.

Попробовать бесплатно

Похожие статьи