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

Чистый код: анализ и рефакторинг в реальных проектах

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

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

Давай разберёмся, как делать рефакторинг правильно.

Чистый код — это не эстетика, это экономика

Для начала честный разговор. Почему вообще рефакторинг? Потому что грязный код стоит денег. Много денег.

Когда я работал в стартапе, наш backend был написан на Node.js с копипастой функций в каждом роуте. Параллельно занимался рефакторингом, вынес логику в сервисы, избавился от дублирования. За три недели:

Это не красиво. Это просто выгодно.

Чистый код — это код, который:

Вот и всё. Остальное — детали.

Code smells: как узнать, что код уже грязный

Есть такая штука — code smells. Это не ошибки и не баги. Это сигналы, что что-то не так. Запах, если угодно. Ты не видишь проблему с первого взгляда, но чувствуешь.

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

Дублирование (DRY violation)

# Плохо
def calculate_user_tax():
    income = get_income()
    expenses = get_expenses()
    taxable = income - expenses
    tax = taxable * 0.13
    return tax

def calculate_company_tax():
    income = get_company_income()
    expenses = get_company_expenses()
    taxable = income - expenses
    tax = taxable * 0.15
    return tax

Видишь паттерн? Логика расчёта одна и та же, только коэффициент разный. Джуниор это не видит. Сеньор это чует на запах.

# Хорошо
def calculate_tax(income, expenses, tax_rate):
    taxable = income - expenses
    return taxable * tax_rate

user_tax = calculate_tax(get_income(), get_expenses(), 0.13)
company_tax = calculate_tax(get_company_income(), get_company_expenses(), 0.15)

Длинные функции (God Function)

Функция, которая делает три дела — это не функция, это сценарий. По моему опыту, если функция больше 20-30 строк, пора её дробить.

# Плохо — 50 строк в одной функции
def process_order(order_id):
    order = db.get_order(order_id)
    
    # Валидация
    if not order.items:
        raise ValueError("No items")
    # ещё 10 строк валидации
    
    # Расчёты
    total = sum(item.price * item.qty for item in order.items)
    # ещё 15 строк расчётов
    
    # Отправка
    email.send(order.customer_email, f"Order {order_id} confirmed")
    # ещё 10 строк отправки
    
    # Логирование
    logger.info(f"Order {order_id} processed")
    # ещё логирование

# Хорошо — разбито на части
def process_order(order_id):
    order = db.get_order(order_id)
    validate_order(order)
    total = calculate_order_total(order)
    notify_customer(order, total)
    log_order_processing(order_id)

Длинные параметры (Long Parameter List)

Если функция принимает 5+ параметров — это звонок. Обычно это значит, что нужен объект.

# Плохо
def create_invoice(customer_id, customer_name, customer_email, 
                  amount, currency, date, description):
    pass

# Хорошо
def create_invoice(customer: Customer, invoice_data: InvoiceData):
    pass

Магические числа

# Плохо
if user.age > 18 and user.experience_years > 2:
    salary = user.base_salary * 1.25

# Хорошо
MIN_AGE_FOR_RAISE = 18
MIN_EXPERIENCE_FOR_RAISE = 2
SENIOR_MULTIPLIER = 1.25

if user.age > MIN_AGE_FOR_RAISE and user.experience_years > MIN_EXPERIENCE_FOR_RAISE:
    salary = user.base_salary * SENIOR_MULTIPLIER

Вложенность (Nested Conditionals)

# Плохо
if user:
    if user.is_active:
        if user.has_permission('edit'):
            if document.owner_id == user.id:
                return True

# Хорошо (ранние выходы)
if not user:
    return False
if not user.is_active:
    return False
if not user.has_permission('edit'):
    return False
if document.owner_id != user.id:
    return False
return True

Это работает лучше. Меньше нестинга, проще читать.

Как анализировать код: инструменты и методы

Ручной анализ — это медленно и ненадёжно. Нужны инструменты.

Статические анализаторы

Для Python — Pylint, Flake8, Black. Для JavaScript — ESLint. Они ловят половину проблем автоматически.

# Запуск Flake8
flake8 app/ --max-line-length=120

# Запуск Black (автоматический форматер)
black app/

Метрики кода

Смотри на эти цифры:

Для Python есть radon:

radon cc app/ -a  # Cyclomatic complexity
radon mi app/     # Maintainability Index

Code review как анализ

Честно? Самый эффективный способ — это когда код смотрит человек. Автоматика ловит синтаксис, а человек видит логику.

На Distiq мы как раз автоматизировали эту часть. AI-бот анализирует каждый PR и выловит те code smells, которые я описал выше. Плюс найдёт потенциальные баги и уязвимости. Это не заменит code review, но сильно его ускорит.

Методы рефакторинга: пошаговая инструкция

Рефакторинг без плана — это путь к боли. Нужна стратегия.

Метод 1: Микро-рефакторинг (маленькие шаги)

Это самый безопасный способ. Ты меняешь маленький кусок, прогоняешь тесты, коммитишь. Потом следующий.

# Шаг 1: Переименовать переменную
result = process_data(x)  # Было
result = process_data(user_input)  # Стало

# Шаг 2: Вынести магическое число
if len(user_input) > 255:  # Было
MAX_INPUT_LENGTH = 255
if len(user_input) > MAX_INPUT_LENGTH:  # Стало

# Шаг 3: Разбить функцию
def validate_and_process():  # Было
    validate(user_input)  # Новое
    process(user_input)   # Новое

Каждый шаг — зелёные тесты. Спокойно спишь ночью.

Метод 2: Extract Method

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

# Было
def create_user(data):
    user = User(data['name'], data['email'])
    user.created_at = datetime.now()
    user.updated_at = datetime.now()
    db.session.add(user)
    db.session.commit()
    logger.info(f"User created: {user.id}")
    return user

# Стало
def create_user(data):
    user = _build_user(data)
    _save_user(user)
    _log_user_creation(user)
    return user

def _build_user(data):
    user = User(data['name'], data['email'])
    user.created_at = datetime.now()
    user.updated_at = datetime.now()
    return user

def _save_user(user):
    db.session.add(user)
    db.session.commit()

def _log_user_creation(user):
    logger.info(f"User created: {user.id}")

Теперь каждая функция делает одно. Тестировать проще. Переиспользовать проще.

Метод 3: Replace Magic Number with Named Constant

# Было
if user.failed_attempts >= 5:
    user.is_locked = True

# Стало
MAX_FAILED_LOGIN_ATTEMPTS = 5

if user.failed_attempts >= MAX_FAILED_LOGIN_ATTEMPTS:
    user.is_locked = True

Просто? Да. Работает? Да.

Метод 4: Consolidate Duplicate Code

Если видишь, что один и тот же код повторяется в разных местах — это нужно объединить. Я это проверял на пяти проектах: 80% багов потом вводились именно в эти дублированные куски.

# Было (в разных местах)
def get_active_users():
    users = User.query.filter(User.is_active == True).all()
    return users

def get_active_admins():
    admins = Admin.query.filter(Admin.is_active == True).all()
    return admins

# Стало
def get_active_entities(model):
    return model.query.filter(model.is_active == True).all()

get_active_users = lambda: get_active_entities(User)
get_active_admins = lambda: get_active_entities(Admin)

Метод 5: Introduce Parameter Object

Когда функция принимает кучу параметров — собери их в объект.

# Было
def create_order(customer_id, customer_name, customer_email,
                items, total, tax, shipping_address):
    pass

# Стало
class OrderData:
    def __init__(self, customer, items, total, tax, shipping_address):
        self.customer = customer
        self.items = items
        self.total = total
        self.tax = tax
        self.shipping_address = shipping_address

def create_order(order_data: OrderData):
    pass

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

Это самый важный раздел. Я видел, как хороший рефакторинг превращался в кошмар.

Правило 1: Тесты должны быть перед рефакторингом

Если у тебя нет тестов — сначала напиши тесты, потом рефакти. Иначе ты не поймёшь, что сломал.

# Сначала напиши тест
def test_calculate_user_tax():
    income = 100000
    expenses = 20000
    tax = calculate_tax(income, expenses, 0.13)
    assert tax == 10400  # (100000 - 20000) * 0.13

# Потом безопасно рефакти функцию

Правило 2: Маленькие коммиты

Не делай огромный PR на 500 строк. Делай 10 маленьких. Легче ревьюить, легче откатывать, если что.

git commit -m "Extract _validate_order to separate function"
git commit -m "Extract _calculate_total to separate function"
git commit -m "Rename variable 'tmp' to 'temporary_result'"

Правило 3: CI/CD должен быть зелёным

Каждый коммит — зелёные тесты, зелёный линтер, зелёный type checker (если Python/TypeScript).

# .github/workflows/ci.yml
- name: Run tests
  run: pytest --cov
- name: Run linter
  run: flake8 app/
- name: Type checking
  run: mypy app/

Правило 4: Отделяй рефакторинг от функционала

Не рефакти и не добавляй фичу в одном PR. Это убивает ревьюер. Делай два PR.

PR #1: Refactor: Extract _calculate_total function (без изменения поведения)
PR #2: Feature: Add discount logic (использует новую функцию из PR #1)

Правило 5: Мониторь метрики после релиза

Рефакторинг может скрыть проблему производительности. После релиза смотри на:

Если что-то подскочило — откатывай.

Практический пример: рефакторинг реального кода

Давай я покажу реальный пример. Код из боевого проекта (немного упрощённый).

Было (грязный код):

def process_payment(payment_id, user_id, amount, currency, card_token):
    # Валидация
    if not payment_id or not user_id or not amount:
        raise ValueError("Invalid input")
    
    # Получение платежа
    p = db.get_payment(payment_id)
    if not p:
        raise ValueError("Payment not found")
    
    # Проверка пользователя
    u = db.get_user(user_id)
    if not

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

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

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

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