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

Рефакторинг кода по Роберту Мартину: как переписать код без багов

Я помню свой первый big refactor. Это было в 2015-м, в одном стартапе. Блок кода на Python из 300 строк, где всё переплетено со всем. Никто не знал, что там раб

Я помню свой первый big refactor. Это было в 2015-м, в одном стартапе. Блок кода на Python из 300 строк, где всё переплетено со всем. Никто не знал, что там работает, а что нет. Я влез туда героем, хотел переписать всё красиво. Результат? Потратил неделю, сломал две фичи, и в итоге откатили всё назад.

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

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

Что такое рефакторинг по Мартину и почему это не переписывание

Роберт Мартин в своих книгах (Clean Code, The Clean Coder) делает упор на одно: рефакторинг — это улучшение внутренней структуры кода без изменения его поведения. Ключевое слово: поведение не меняется.

Многие путают это с переписыванием. Вот в чём разница.

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

По Мартину, правильный рефакторинг — это всегда маленькие шаги. Переименовал переменную — сразу прогнал тесты. Извлёк метод — тесты снова. Перетащил класс в другой модуль — и опять проверка.

Это бывает скучно. Это бывает долго. Но это работает.

На одном проекте мы внедрили именно такой подход: каждый коммит содержит одно атомарное изменение. Результат? За полгода переделали половину кодовой базы, и ноль критических багов при этом. Сравните это с моим первым опытом.

Код smells: как узнать, что код нуждается в рефакторинге

Мартин ввёл понятие "код smells" — это признаки того, что с кодом что-то не так. Не ошибки, не баги, а именно запахи. Вот основные:

Дублирование кода (DRY violation). Если ты видишь один и тот же блок в трёх местах — это красный флаг. Нужно извлечь в отдельный метод или класс. Вот пример:

# До: одна и та же логика в разных местах
def process_user_data(user):
    if not user.email:
        raise ValueError("Email required")
    if not user.name:
        raise ValueError("Name required")
    return user

def process_admin_data(admin):
    if not admin.email:
        raise ValueError("Email required")
    if not admin.name:
        raise ValueError("Name required")
    return admin

# После: извлекли в отдельную функцию
def validate_person(person, fields):
    for field in fields:
        if not getattr(person, field, None):
            raise ValueError(f"{field} required")
    return person

def process_user_data(user):
    return validate_person(user, ['email', 'name'])

def process_admin_data(admin):
    return validate_person(admin, ['email', 'name'])

Слишком длинные методы. Если функция на 50+ строк, её сложно тестировать, сложно понимать, в ней много причин для изменения. Мартин рекомендует, чтобы метод помещался на экран целиком.

Слишком много параметров. Если в функцию передаётся 5+ параметров, это значит, что функция делает слишком много или параметры логически связаны и их стоит обернуть в объект.

# Плохо: слишком много параметров
def create_order(user_id, product_id, quantity, discount, tax_rate, shipping_cost, payment_method):
    pass

# Хорошо: параметры обёрнуты в объекты
class OrderDetails:
    def __init__(self, product_id, quantity, discount):
        self.product_id = product_id
        self.quantity = quantity
        self.discount = discount

def create_order(user_id, details, shipping, payment):
    pass

Классы, которые делают слишком много (God Objects). Если класс знает про биллинг, логику пользователей, отправку писем и вообще всё, значит нарушена принцип единственной ответственности (SRP). Нужно разбить на несколько классов.

Условная логика, которая слишком сложная. Если у тебя есть 5 уровней вложенных if-ов, это не просто плохо читается — в таком коде легко завести баг.

# Плохо: глубокая вложенность
def process_payment(order):
    if order.is_valid():
        if order.user.is_active:
            if order.amount > 0:
                if order.payment_method == 'card':
                    if validate_card(order.card):
                        process_card_payment(order)
                        
# Хорошо: ранний выход (early return)
def process_payment(order):
    if not order.is_valid():
        return
    if not order.user.is_active:
        return
    if order.amount <= 0:
        return
    if order.payment_method != 'card':
        return
    if not validate_card(order.card):
        return
    process_card_payment(order)

Мартин советует: если ты видишь эти запахи, не паникуй. Это просто сигнал, что нужно потратить время на рефакторинг. Но не обязательно сразу все исправлять — начни с самого болючего.

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

Мартин выделяет несколько базовых техник. Они простые, но если их комбинировать, можно переделать даже очень запущенный код.

Extract Method — самая частая. Ты берёшь блок кода и вытягиваешь его в отдельный метод. Это снижает сложность, делает код читаемым.

# До
def generate_report(users):
    result = []
    for user in users:
        if user.is_active and user.created_at > datetime(2023, 1, 1):
            salary = user.base_salary * (1 + user.bonus / 100)
            result.append({
                'name': user.name,
                'salary': salary,
                'department': user.department
            })
    return result

# После
def generate_report(users):
    active_users = filter_active_recent_users(users)
    return [create_user_report(u) for u in active_users]

def filter_active_recent_users(users):
    cutoff_date = datetime(2023, 1, 1)
    return [u for u in users if u.is_active and u.created_at > cutoff_date]

def create_user_report(user):
    return {
        'name': user.name,
        'salary': calculate_salary(user),
        'department': user.department
    }

def calculate_salary(user):
    return user.base_salary * (1 + user.bonus / 100)

Видишь разницу? Теперь каждая функция делает одно. Её легче тестировать, легче переиспользовать, легче понимать, что она делает.

Replace Magic Numbers with Named Constants. Числа в коде — это тайна. Что означает 0.15? Налог? Коммиссия? Непонятно.

# Плохо
def calculate_price(base_price):
    return base_price * 1.18 + 50

# Хорошо
TAX_RATE = 0.18
DELIVERY_FEE = 50

def calculate_price(base_price):
    return base_price * (1 + TAX_RATE) + DELIVERY_FEE

Extract Class. Когда класс делает слишком много, ты вытягиваешь часть его в отдельный класс.

# До: User делает всё
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email
    
    def send_email(self, subject, body):
        # логика отправки письма
        pass
    
    def validate_email(self):
        # логика валидации
        pass

# После: разбили ответственность
class User:
    def __init__(self, name, email_address):
        self.name = name
        self.email_address = email_address

class EmailService:
    def send(self, email, subject, body):
        pass
    
    def validate(self, email):
        pass

Introduce Parameter Object. Когда функция принимает кучу связанных параметров, обёртываешь их в класс.

# До
def book_hotel(user_id, check_in, check_out, rooms, guests, budget):
    pass

# После
class BookingRequest:
    def __init__(self, check_in, check_out, rooms, guests, budget):
        self.check_in = check_in
        self.check_out = check_out
        self.rooms = rooms
        self.guests = guests
        self.budget = budget

def book_hotel(user_id, request):
    pass

Это особенно полезно, когда у тебя много методов с одинаковыми параметрами. Один раз создал объект, потом передаёшь его везде.

Replace Conditional with Polymorphism. Если у тебя длинная цепочка if-ов, которая проверяет тип объекта, это кандидат на полиморфизм.

# Плохо: логика размазана по условиям
class PaymentProcessor:
    def process(self, payment):
        if payment.type == 'card':
            self.process_card(payment)
        elif payment.type == 'paypal':
            self.process_paypal(payment)
        elif payment.type == 'crypto':
            self.process_crypto(payment)

# Хорошо: логика в классах
class Payment:
    def process(self):
        raise NotImplementedError

class CardPayment(Payment):
    def process(self):
        # логика для карты

class PayPalPayment(Payment):
    def process(self):
        # логика для PayPal

class CryptoPayment(Payment):
    def process(self):
        # логика для крипто

Это несколько основных техник. Мартин описывает ещё десятки в своих книгах, но эти пять покрывают примерно 80% случаев.

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

Вот где многие падают. Ты начал рефакторить, а потом понимаешь, что сломал что-то и теперь не знаешь, что именно.

Мартин и Фаулер (которые вместе писали про рефакторинг) выделяют несколько правил.

Правило первое: у тебя должны быть тесты. Не идеальные, не 100%-ное покрытие, но основной функционал должен быть протестирован. Тесты — это твоя подушка безопасности. Если ты что-то сломал, тесты скажут об этом сразу.

Если тестов нет, начни с добавления тестов. Да, это долго. Да, это скучно. Но это спасает жизнь.

# Напиши тесты перед рефакторингом
import pytest

def test_calculate_salary_with_bonus():
    user = User(base_salary=100000, bonus=10)
    assert calculate_salary(user) == 110000

def test_calculate_salary_without_bonus():
    user = User(base_salary=100000, bonus=0)
    assert calculate_salary(user) == 100000

Правило второе: рефакторь маленькими шагами. Не пытайся переписать весь класс за раз. Переименовал переменную — запустил тесты. Извлёк метод — запустил тесты. Один коммит = одно изменение.

Правило третье: используй IDE правильно. Современные IDE (PyCharm, VS Code с плагинами) умеют автоматизировать рефакторинг. Rename, Extract Method, Move — всё это встроено. Используй это, не переписывай вручную.

# Вместо ручного поиска-замены, используй рефакторинг IDE
# PyCharm: Ctrl+Shift+R для Rename
# VS Code: F2 для Rename

Правило четвёртое: коммитьте часто. После каждого маленького изменения — коммит. Это позволит быстро откатиться, если что-то пошло не так.

git commit -m "refactor: extract calculate_salary method"
git commit -m "refactor: rename user_data to active_users"
git commit -m "refactor: move email validation to EmailService"

Правило пятое: code review. Попроси коллегу посмотреть твой рефакторинг. Свежий взгляд часто видит то, что ты пропустил.

На практике это выглядит вот так. Ты видишь код smell, открываешь файл, создаёшь новую ветку, начинаешь рефакторить маленькими шагами. После каждого шага — тесты. После всех шагов — pull request, code review, merge.

Это может занять день на то, что выглядит как пятиминутная задача. Но результат — код, в который ты можешь верить. Это стоит времени.

Рефакторинг в 1С и специфика

Если ты работаешь с 1С (и много разработчиков в России работают), рефакторинг там особый. 1С — это не совсем стандартный язык, и подход нужен другой.

Основные проблемы в 1С:

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

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

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

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