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

Рефакторинг кода по Фаулеру: практическое руководство для разработчиков

Когда я впервые прочитал "Refactoring" Мартина Фаулера, был 2019-й год. Я сидел в офисе Яндекса, смотрел на кодовую базу, которая копилась 10 лет, и понял: я де

Когда я впервые прочитал "Refactoring" Мартина Фаулера, был 2019-й год. Я сидел в офисе Яндекса, смотрел на кодовую базу, которая копилась 10 лет, и понял: я делал всё не так. Не плохо, но неправильно. Фаулер описал то, что я интуитивно ощущал, но никогда не формулировал в слова.

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

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

Что такое рефакторинг по Фаулеру

Мартин Фаулер дал чёткое определение: рефакторинг — это переструктурирование существующего кода без изменения его внешнего поведения. Ключевое слово здесь — "без изменения". Вы не добавляете фичи. Вы не ловите новые баги. Вы делаете ровно одно: улучшаете структуру.

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

На практике это выглядит так:

# ДО рефакторинга
def calculate_price(items):
    total = 0
    for item in items:
        if item['type'] == 'book':
            total += item['price'] * 0.9
        elif item['type'] == 'food':
            total += item['price'] * 0.95
        else:
            total += item['price']
    if len(items) > 10:
        total *= 0.95
    return total

# ПОСЛЕ рефакторинга
def calculate_price(items):
    total = sum(get_item_price(item) for item in items)
    return apply_bulk_discount(total, len(items))

def get_item_price(item):
    discount_rate = get_discount_rate(item['type'])
    return item['price'] * discount_rate

def get_discount_rate(item_type):
    rates = {'book': 0.9, 'food': 0.95}
    return rates.get(item_type, 1.0)

def apply_bulk_discount(total, item_count):
    return total * 0.95 if item_count > 10 else total

Функция делает то же самое. Но теперь вы видите логику. Она разложена на части. Каждая часть отвечает за одно. Если надо добавить скидку для электроники — добавляете одну строку в словарь. Не переписываете всю функцию.

Поведение не изменилось. Входные и выходные данные те же. Но код теперь живой, а не застывший.

Code smells: когда пора рефакторить

Фаулер ввёл понятие "code smells" — признаки того, что код нуждается в рефакторинге. Не баги. Не ошибки. Просто запахи, которые говорят: здесь что-то не правильно.

Самые частые:

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

# Это плохо
def process_user_order(user_id, items):
    user = fetch_user(user_id)
    if user is None:
        raise Exception("User not found")
    
    total = 0
    for item in items:
        product = fetch_product(item['id'])
        if product is None:
            raise Exception("Product not found")
        total += product['price'] * item['quantity']
    
    tax = total * 0.1
    total += tax
    
    order = create_order(user_id, items, total)
    send_confirmation_email(user['email'], order)
    update_inventory(items)
    
    return order

# Это хорошо
def process_user_order(user_id, items):
    user = validate_user(user_id)
    total = calculate_order_total(items)
    order = create_order(user_id, items, total)
    send_confirmation_email(user['email'], order)
    update_inventory(items)
    return order

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

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

# Плохо: один класс делает всё
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email
    
    def validate_email(self):
        # Валидация
        pass
    
    def send_email(self):
        # Отправка письма
        pass
    
    def save_to_database(self):
        # Работа с БД
        pass
    
    def generate_report(self):
        # Отчёты
        pass

# Хорошо: разделили ответственность
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email

class EmailValidator:
    @staticmethod
    def validate(email):
        pass

class EmailService:
    def send(self, email, message):
        pass

class UserRepository:
    def save(self, user):
        pass

class UserReporter:
    def generate(self, user):
        pass

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

# Плохо
def create_invoice(user_id, user_name, user_email, user_phone, items, discount, tax_rate, shipping_cost):
    pass

# Хорошо
class InvoiceData:
    def __init__(self, user, items, discount, tax_rate, shipping_cost):
        self.user = user
        self.items = items
        self.discount = discount
        self.tax_rate = tax_rate
        self.shipping_cost = shipping_cost

def create_invoice(invoice_data):
    pass

Условная логика, которая запутана. Вложенные if на три уровня — это красный флаг. Используйте guard clauses.

# Плохо
def process_payment(user, amount):
    if user is not None:
        if user.is_active:
            if amount > 0:
                if user.balance >= amount:
                    user.balance -= amount
                    return True
    return False

# Хорошо
def process_payment(user, amount):
    if not user or not user.is_active:
        return False
    if amount <= 0:
        return False
    if user.balance < amount:
        return False
    
    user.balance -= amount
    return True

Видите разницу? Во втором варианте вы сразу понимаете, при каких условиях платёж не пройдёт. Не надо следить за вложенностью.

Техники рефакторинга: что на что менять

Фаулер описал десятки техник. Я расскажу о самых полезных.

Extract Method. Самая частая. Берёте кусок кода, выделяете в отдельную функцию, даёте ей понятное имя. Вуаля.

# ДО
def print_invoice(invoice):
    print("INVOICE")
    print("=" * 50)
    total = 0
    for item in invoice['items']:
        print(f"{item['name']}: {item['price']} x {item['quantity']}")
        total += item['price'] * item['quantity']
    print("=" * 50)
    print(f"Total: {total}")

# ПОСЛЕ
def print_invoice(invoice):
    print_header()
    print_items(invoice['items'])
    print_total(invoice['items'])

def print_header():
    print("INVOICE")
    print("=" * 50)

def print_items(items):
    for item in items:
        print(f"{item['name']}: {item['price']} x {item['quantity']}")

def print_total(items):
    total = sum(item['price'] * item['quantity'] for item in items)
    print("=" * 50)
    print(f"Total: {total}")

Replace Magic Numbers with Named Constants. Числа без смысла — это зло.

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

# Хорошо
MIN_AGE = 18
MIN_EXPERIENCE_YEARS = 3
SENIOR_SALARY_MULTIPLIER = 1.25

if user.is_senior():
    salary = base_salary * SENIOR_SALARY_MULTIPLIER

def is_senior(user):
    return user.age >= MIN_AGE and user.experience_years >= MIN_EXPERIENCE_YEARS

Replace Conditional with Polymorphism. Если у вас есть if item_type == 'book' в 10 местах — используйте наследование или интерфейсы.

# Плохо: проверка типа везде
def get_discount(item):
    if item['type'] == 'book':
        return 0.1
    elif item['type'] == 'food':
        return 0.05
    else:
        return 0

# Хорошо: каждый тип знает свою скидку
class Item:
    def get_discount(self):
        raise NotImplementedError

class Book(Item):
    def get_discount(self):
        return 0.1

class Food(Item):
    def get_discount(self):
        return 0.05

class Other(Item):
    def get_discount(self):
        return 0

Introduce Parameter Object. Когда одни и те же параметры передаются повсеместно — соберите их в класс.

# Плохо
def create_user(name, email, phone):
    pass

def send_welcome_email(name, email, phone):
    pass

def save_to_database(name, email, phone):
    pass

# Хорошо
class UserData:
    def __init__(self, name, email, phone):
        self.name = name
        self.email = email
        self.phone = phone

def create_user(user_data):
    pass

def send_welcome_email(user_data):
    pass

def save_to_database(user_data):
    pass

Rename. Просто переименуйте переменную, если её имя не понятно. Это звучит просто, но работает чудесно.

# Плохо
def calc(a, b, c):
    return a + b * c

# Хорошо
def calculate_total_price(base_price, item_count, discount_rate):
    return base_price + item_count * discount_rate

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

Вот это самое важное. Рефакторинг без тестов — это самоубийство.

Шаг первый: напишите тесты для существующего кода. Даже если его нет. Если код работает — напишите тесты, которые проверяют его текущее поведение. Это ваша подушка безопасности.

# Тест для функции выше
def test_calculate_price():
    items = [
        {'type': 'book', 'price': 100},
        {'type': 'food', 'price': 50}
    ]
    assert calculate_price(items) == 135  # 100*0.9 + 50*0.95

def test_calculate_price_with_bulk_discount():
    items = [{'type': 'other', 'price': 100}] * 11
    assert calculate_price(items) == 1045  # 1100 * 0.95

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

# Процесс выглядит так
git add .
git commit -m "Extract discount calculation"
npm test  # или pytest, или что у вас
# Если всё зелёное — идём дальше
git add .
git commit -m "Extract bulk discount logic"
npm test
# И так далее

Шаг третий: используйте инструменты. IDE помогут. PyCharm, VS Code, IntelliJ — все они умеют рефакторить. Используйте встроенные команды: Extract Method, Rename, Move. Они безопаснее, чем руками.

Шаг четвёртый: просить code review. Даже рефакторинг — это не одиночный процесс. Коллега может заметить, что вы потеряли какой-то edge case или усложнили логику.

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

Когда начинать рефакторинг

Честно? Постоянно. Но есть моменты, когда это критично.

Перед добавлением новой фичи. Если вы видите, что код грязный, и вам сложно добавить фичу — сначала рефакторьте. Будет быстрее. На одном проекте мы потратили день на рефакторинг, зато новая фича была готова на следующий день вместо трё

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

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

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

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