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

Python рефакторинг: как переписывать код и не сойти с ума

Рефакторинг — это когда ты меняешь код, но не меняешь его поведение. Звучит просто. На практике это боль, страдания и ночные деплои, потому что "ну тут же одна

Рефакторинг — это когда ты меняешь код, но не меняешь его поведение. Звучит просто. На практике это боль, страдания и ночные деплои, потому что "ну тут же одна маленькая правка".

За 10 лет в разработке я refactor-ил всё: от легаси на PHP 5 до микросервисов на Go. Но Python занимает особое место. В нём легко писать. И так же легко писать плохо. Динамическая типизация, отсутствие компилятора — всё это создаёт иллюзию, что код работает. Пока не перестаёт.

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

Когда вообще пора рефакторить

Удивительно, но большинство команд откладывает рефакторинг до бесконечности. "У нас дедлайн", "потом перепишем", "работает же". Знакомо?

По моему опыту, есть три чётких сигнала:

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

Onboarding новых сотрудников превращается в ад. Джуниор смотрит на код и спрашивает: "А это что?". Ты отвечаешь: "Не знаю, это писал Вася три года назад, он уволился". И оба вы сидите и пытаетесь понять, что происходит.

Code review превращается в битву. Ревьюеры пишут "что это за херня?", авторы защищаются "ну работает же". Все устали.

Если вы узнали себя хотя бы в одном пункте — поздравляю, пора refactor-ить. Рефакторинг кода приложения нельзя откладывать в долгий ящик. Чем дольше ждёте, тем дороже это будет стоить.

Code smells: ищем, что пахнет

Прежде чем рефакторить, надо понять, что именно. Code smells — это не баги. Код работает. Но он попахивает.

Длинные методы. Функция на 200 строк, которая делает всё: валидирует данные, обращается к базе, отправляет email, логирует. Любое изменение — это рулетка.

Дублирование. Один и тот же код скопирован в пять мест с минимальными изменениями. Исправил в одном месте — сломал в четырёх других.

Магические числа и строки. if status == 3:, timeout = 86400. Что это за числа? Почему именно они? Через полгода никто не вспомнит.

Глубокая вложенность. Три-четыре уровня if-ов внутри for внутри while. Код превращается в ёлку, и читать его невозможно.

Посмотрим на пример.

# До рефакторинга — типичный legacy код
def process_order(order_data):
    result = {}
    if order_data:
        if 'items' in order_data:
            if len(order_data['items']) > 0:
                total = 0
                for item in order_data['items']:
                    if 'price' in item and 'quantity' in item:
                        total += item['price'] * item['quantity']
                result['total'] = total
                if total > 10000:
                    result['discount'] = total * 0.1
                else:
                    result['discount'] = 0
                result['status'] = 'processed'
            else:
                result['error'] = 'No items'
        else:
            result['error'] = 'Items missing'
    else:
        result['error'] = 'Empty order'
    return result

Код работает. Но давайте честно — это больно читать. Вложенные условия, магические числа, смешанная логика.

# После рефакторинга
DISCOUNT_THRESHOLD = 10000
DISCOUNT_RATE = 0.1

def process_order(order_data):
    if not order_data:
        return {'error': 'Empty order'}
    
    items = order_data.get('items', [])
    if not items:
        return {'error': 'No items'}
    
    total = calculate_total(items)
    discount = calculate_discount(total)
    
    return {
        'total': total,
        'discount': discount,
        'status': 'processed'
    }

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

def calculate_discount(total):
    return total * DISCOUNT_RATE if total > DISCOUNT_THRESHOLD else 0

Стало больше кода по количеству строк. Но каждая функция делает одно. process_order — оркестрирует процесс. calculate_total — считает сумму. calculate_discount — вычисляет скидку. Читается за секунду. Тестируется легко.

Методы рефакторинга, которые реально работают

Извлечение метода — самый частый приём в моей практике. Когда функция делает больше одной вещи — разбивай. Звучит банально, но большинство разработчиков этого не делает.

# До — один метод делает всё
def send_report(user_id):
    user = db.get_user(user_id)
    if not user or not user.email:
        return False
    
    data = db.get_user_stats(user_id)
    report = generate_pdf(data)
    
    subject = f"Отчёт для {user.name}"
    body = f"Во вложении ваш отчёт за {datetime.now().strftime('%Y-%m-%d')}"
    
    return email_service.send(user.email, subject, body, report)

# После — разделение ответственности
def send_report(user_id):
    user = get_user_or_fail(user_id)
    if not user:
        return False
    
    report = build_user_report(user_id)
    return deliver_report(user, report)

def get_user_or_fail(user_id):
    user = db.get_user(user_id)
    return user if user and user.email else None

def build_user_report(user_id):
    data = db.get_user_stats(user_id)
    return generate_pdf(data)

def deliver_report(user, report):
    subject = f"Отчёт для {user.name}"
    body = f"Во вложении ваш отчёт за {datetime.now().strftime('%Y-%m-%d')}"
    return email_service.send(user.email, subject, body, report)

Замена условных операторов полиморфизмом — когда у вас появляется if type == 'A': ... elif type == 'B': .... Это классика. И это тоже code smell.

# До — растущий switch/if-else
def calculate_price(product, user):
    if product.type == 'standard':
        price = product.base_price
    elif product.type == 'premium':
        price = product.base_price * 1.5
    elif product.type == 'sale':
        price = product.base_price * 0.7
    else:
        price = product.base_price
    
    if user.is_vip:
        price *= 0.9
    
    return price

# После — полиморфизм
from abc import ABC, abstractmethod

class PricingStrategy(ABC):
    @abstractmethod
    def calculate(self, base_price: float) -> float:
        pass

class StandardPricing(PricingStrategy):
    def calculate(self, base_price):
        return base_price

class PremiumPricing(PricingStrategy):
    def calculate(self, base_price):
        return base_price * 1.5

class SalePricing(PricingStrategy):
    def calculate(self, base_price):
        return base_price * 0.7

PRICING_STRATEGIES = {
    'standard': StandardPricing(),
    'premium': PremiumPricing(),
    'sale': SalePricing(),
}

def calculate_price(product, user):
    strategy = PRICING_STRATEGIES.get(product.type, StandardPricing())
    price = strategy.calculate(product.base_price)
    
    if user.is_vip:
        price *= 0.9
    
    return price

Да, кода больше. Но теперь добавить новый тип ценообразования — это создать один класс и добавить строку в словарь. Не нужно трогать функцию calculate_price. Не нужно ломать существующую логику. Open/Closed Principle в действии.

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

Худшее, что можно сделать — refactor-ить без тестов. На одном проекте мы решили "просто почистить код" перед релизом. Резульат? Откат в 2 часа ночи, злой заказчик и обещание "больше никогда".

Правила, которые спасут вас:

Пиши тесты перед рефакторингом. Хотя бы базовые. Нужно понимать, что код должен делать, прежде чем его менять. Refactor-инг кода Python без тестов — это рулетка.

Делай маленькие изменения. Не переписывай весь модуль за раз. Один refactor — один коммит. Переименовал переменную — закоммитил. Вынес метод — закоммитил.

Используй инструменты статического анализа. mypy, pylint, ruff — они ловят ошибки, которые глаз уже не видит.

# Минимальный набор для проверки перед коммитом
ruff check .
mypy src/
pytest tests/ -v

Code review обязательно. Даже если ты сеньор. Особенно если сеньор. Свежий глаз заметит то, что ты пропустил за привыканием к коду.

Инструменты для рефакторинга

Честно? Большинство инструментов для рефакторинг кода онлайн — это красиво завёрнутые AI-ассистенты. Они могут подсказать, но не сделают работу за тебя.

Что реально помогает:

IDE. PyCharm и VS Code с Python-расширениями умеют переименовывать, извлекать методы, менять сигнатуры. Используй встроенные рефакторинги — они работают с AST и меньше рискуют сломать код, чем поиск-замена.

Автоматические форматтеры. black, isort, autopep8. Не рефакторинг в чистом виде, но наводит порядок. Запускай перед каждым коммитом.

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/psf/black
    rev: 23.12.0
    hooks:
      - id: black
  - repo: https://github.com/pycqa/isort
    rev: 5.13.0
    hooks:
      - id: isort
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.1.8
    hooks:
      - id: ruff

AI для рефакторинга кода. Здесь аккуратно. LLM хороши для предложений, но не для слепого доверия. Copilot, ChatGPT могут показать альтернативную реализацию. Но ты всё равно должен понимать, что происходит.

На практике я использую AI как второго мнения. Написал код — спросил у модели: "Как это можно улучшить?". Иногда предлагает дельное. Иногда полную ерунду. Фильтруй.

Автоматическое code review

Рефакторинг — это процесс. Не разовое действие. И хорошо бы иметь систему, которая подскажет, когда код deteriorates.

У нас в команде появился бот, который автоматически ревьюит каждый MR. Он не заменяет человека, но ловит типичные проблемы: неиспользуемые импорты, дублирование кода, потенциальные баги. Сэкономили кучу времени на code review.

Distiq — это как раз такой инструмент. Российский сервис AI code review для GitLab, GitHub и GitVerse. Интегрируется за пару минут через webhook, анализирует код и пишет инлайн-комментарии. Проверяет Python, JavaScript, TypeScript, другие языки. Серверы в РФ, данные не уходят.

Я рекомендую настроить его на проект. Не потому что это "правильно", а потому что реально экономит время. Рефакторинг кода Python превращается из боли в рутину. А рутина лучше, чем боль.

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

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

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

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