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

В реальных проектах, особенно когда команда растёт, а сроки сжимаются, накопленный «грязный» код становится бомбой замедленного действия. Многие разработчики в такой ситуации паникуют и предлагают: «Давайте просто перепишем всё с нуля». Это ошибка, которая может стоить проекту месяцев работы и даже привести к его краху. Рефакторинг — это не переписывание, а системное улучшение структуры кода без изменения его внешнего поведения. Он делает код чище, понятнее и легче в поддержке, сохраняя при этом все функции, которые он выполняет.

Я прошёл путь от новичка, который часто писал «как получится», до тимлида и архитектора. Мой опыт показывает: регулярный рефакторинг — это не роскошь, а необходимость. В этой статье я подробно разберу, что такое рефакторинг, зачем он нужен, какие техники работают лучше всего, как проводить его безопасно и какие ошибки чаще всего совершают джуниоры. Вы получите практические чек-листы, примеры кода и пошаговые инструкции, которые сможете применить в своих проектах сразу после чтения.

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

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

Часто рефакторинг путают с:

  • Оптимизацией — изменением кода для повышения скорости работы или снижения потребления ресурсов. Рефакторинг может косвенно улучшить производительность, но его главная цель — не скорость, а качество кода.
  • Переписыванием (Rewriting) — полным удалением старого кода и созданием нового. Это радикальный шаг, который требует огромных ресурсов и часто приводит к потере функционала.
  • Исправлением багов — устранением ошибок в логике программы. Рефакторинг не меняет логику, он лишь улучшает способ её реализации.

Почему рефакторинг важен для разработчика и проекта

Вот ключевые причины, почему рефакторинг должен быть частью ежедневной работы разработчика:

  1. Снижение стоимости поддержки. Код, который легко читать и изменять, требует меньше времени на исправление ошибок и добавление новых функций. В долгосрочной перспективе это экономит огромные деньги. По моим наблюдениям, команда, которая рефакторит регулярно, тратит на фичи в среднем на 30% меньше времени уже через пару спринтов.
  2. Увеличение скорости разработки. Когда структура кода понятна, новые разработчики быстрее встраиваются в проект, а старые — быстрее находят нужные места для изменений.
  3. Предотвращение накопления «грязного кода». Без рефакторинга проект быстро превращается в «лапшу» (spaghetti code), где каждая новая функция усложняет систему ещё сильнее. Это особенно заметно в монолитах, которые живут больше трёх лет.
  4. Поддержка E-E-A-T (Experience, Expertise, Authoritativeness, Trustworthiness). Качественный код демонстрирует опыт и экспертность разработчика, что важно для карьерного роста и доверия в команде.
  5. Упрощение тестирования. Чистый код проще тестировать, так как каждая функция имеет ясные границы и не зависит от посторонних частей. Модульные тесты пишутся быстрее, а интеграционные — реже ломаются.

Пример из жизни: В одном из моих проектов мы добавляли новую функцию к модулю, который уже работал 3 года. Код был настолько запутанным, что на изменение потребовалось 2 недели. После рефакторинга этого модуля аналогичное изменение заняло 2 часа. Разница — в 10 раз. И это при том, что сам рефакторинг занял всего день.

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

Рефакторинг не должен быть случайным событием. Вот ситуации, когда он особенно необходим:

  • Перед добавлением новой функции. Если код сложный, новая функция может усугубить проблему. Лучше сначала привести в порядок существующую логику, чтобы не усложнять архитектуру.
  • После обнаружения бага. Если баг был вызван сложной структурой, рефакторинг может помочь избежать подобных ошибок в будущем. Я часто провожу «рефакторинг после инцидента», чтобы устранить корневую причину запутанности.
  • При смене команды разработчиков. Новые люди могут не понять старый код, и рефакторинг поможет им быстрее вникнуть в проект. Это снижает время онбординга.
  • При росте проекта. Когда проект становится большим, его структура должна быть адаптирована под новые масштабы. Например, монолит перед разделением на микросервисы требует рефакторинга модулей.
  • При планировании масштабирования. Если вы готовите проект к росту, рефакторинг поможет подготовить код к изменениям, сделав зависимости явными.

Важно: Рефакторинг не должен быть «одним большим событием». Лучше делать его постепенно, в процессе разработки, по принципу «маленьких шагов». Я всегда рекомендую закладывать 10–15% времени спринта на небольшие улучшения кода.

Основные техники рефакторинга: от простого к сложному

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

Уровень 1: Базовые техники (для ежедневного использования)

Эти техники просты, но эффективны. Их можно применять в любом проекте, даже в небольших.

1. Удаление дублирования кода (Remove Duplicate Code)

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

Проблема:

def calculate_discount_price(price):
    discount = 0.1
    return price * (1 - discount)

def calculate_sale_discount_price(price):
    discount = 0.1
    return price * (1 - discount)

Рефакторинг:

def apply_discount(price, discount=0.1):
    return price * (1 - discount)

discounted_price = apply_discount(price)
sale_price = apply_discount(price, discount=0.15)

Правило: Если код повторяется 2 раза или больше — объединяйте его. Даже если кажется, что «и так сойдёт», дублирование рано или поздно приведёт к рассинхронизации логики.

2. Замена условных операторов на функции (Replace Conditional with Function)

Сложные условия (особенно с множеством if-else) делают код трудным для чтения. Их можно заменить на отдельные функции с говорящими названиями.

Проблема:

if user.age >= 18 and user.country == "US" and user.has_license:
    can_rent = True
else:
    can_rent = False

Рефакторинг:

def is_eligible_for_rental(user):
    return user.age >= 18 and user.country == "US" and user.has_license

can_rent = is_eligible_for_rental(user)

Правило: Если условие содержит 3+ уровня вложенности или несколько логических операторов — замените его на функцию. Это не только улучшает читаемость, но и позволяет переиспользовать проверку в других местах.

3. Упрощение условий (Simplify Conditional Expressions)

Сложные условия можно упростить, используя логические операторы или функции. Избегайте избыточных сравнений и отрицаний.

Проблема:

if not (user.is_active == False or user.is_blocked == True):
    allow_access()

Рефакторинг:

if user.is_active and not user.is_blocked:
    allow_access()

Правило: Если условие содержит более 2 логических операторов или двойные отрицания — упрощайте его. Старайтесь, чтобы условие читалось как обычное предложение.

4. Замена магических чисел на константы (Replace Magic Numbers with Constants)

Магические числа (числа, которые не имеют смысла в коде) затрудняют понимание. Их нужно заменить на константы с ясными названиями.

Проблема:

if temperature > 30:
    activate_cooling()

Рефакторинг:

MAX_SAFE_TEMPERATURE = 30
if temperature > MAX_SAFE_TEMPERATURE:
    activate_cooling()

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

5. Удаление пустых блоков (Remove Empty Blocks)

Пустые блоки кода (например, пустые if, for, функции) не нужны и могут быть удалены. Они создают ложное впечатление недоделанной логики.

Проблема:

if user.is_admin:
    pass
else:
    show_dashboard()

Рефакторинг:

if not user.is_admin:
    show_dashboard()

Правило: Если блок пуст — удалите его или перепишите условие так, чтобы пустой блок исчез. Пустые блоки — это мусор, который отвлекает при чтении.

Уровень 2: Продвинутые техники (для сложных случаев)

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

1. Разделение большой функции на маленькие (Split Large Function)

Большая функция (более 50 строк) сложна для чтения и тестирования. Её нужно разделить на несколько маленьких, каждая из которых решает одну задачу.

Проблема:

def process_order(order):
    # validation (15 lines)
    ...
    # calculation (20 lines)
    ...
    # notification (20 lines)
    ...

Рефакторинг:

def validate_order(order):
    ...

def calculate_order_total(order):
    ...

def send_notification(order):
    ...

def process_order(order):
    validate_order(order)
    total = calculate_order_total(order)
    send_notification(order)
    return total

Правило: Если функция содержит более 50 строк или несколько уровней абстракции — делите её. Это позволяет тестировать каждую часть отдельно и переиспользовать логику. Я часто применяю это для контроллеров, которые со временем распухают.

2. Замена наследования на композицию (Replace Inheritance with Composition)

Наследование может привести к сложной иерархии и жёсткой связанности. В некоторых случаях его можно заменить на композицию (использование объектов вместо наследования).

Проблема:

class Bird:
    def fly(self):
        ...

class Penguin(Bird):
    def fly(self):
        raise NotImplementedError("Penguins can't fly")

Рефакторинг:

class FlyingBehavior:
    def fly(self):
        ...

class Bird:
    def __init__(self, flying_behavior):
        self.flying = flying_behavior

class Penguin(Bird):
    def __init__(self):
        super().__init__(flying_behavior=None)

Правило: Если наследование создаёт сложную структуру или нарушает принцип подстановки Лисков — замените его на композицию. Это делает архитектуру гибче и понятнее.

3. Внедрение зависимостей (Introduce Dependency Injection)

Зависимости (например, подключение к базе данных, внешние сервисы) должны быть передаваемыми, а не создаваться внутри функции. Это упрощает тестирование и замену реализации.

Проблема:

def get_user_orders(user_id):
    db = MySQLConnection()
    return db.query(f"SELECT * FROM orders WHERE user_id={user_id}")

Рефакторинг:

def get_user_orders(user_id, db_connection):
    return db_connection.query(f"SELECT * FROM orders WHERE user_id={user_id}")

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

4. Замена глобальных переменных на локальные (Replace Global Variables with Local Variables)

Глобальные переменные могут привести к непредсказуемым ошибкам из-за неожиданных изменений состояния. Их нужно заменить на локальные или передавать явно.

Проблема:

TAX_RATE = 0.2

def calculate_total(price):
    return price * (1 + TAX_RATE)

Рефакторинг:

def calculate_total(price, tax_rate=0.2):
    return price * (1 + tax_rate)

Правило: Если переменная используется в нескольких функциях и может быть изменена — замените её на локальную или передавайте параметром. Это делает код более предсказуемым и безопасным при многопоточности.

5. Удаление неиспользуемого кода (Remove Unused Code)

Неиспользуемый код (например, старые функции, закомментированные блоки) не нужен и может быть удалён. Он захламляет проект и сбивает с толку.

Проблема:

# def old_function():
#     return "deprecated"

Рефакторинг: просто удалите этот блок.

Правило: Если код не используется — удалите его. Система контроля версий всегда хранит историю, поэтому не бойтесь потерять что-то важное. Я всегда советую удалять мёртвый код, а не комментировать.

Таблица: Сравнение техник рефакторинга

Техника Сложность Когда применять Пример
Удаление дублирования Низкая Когда код повторяется 2+ раза Объединение функций
Замена условий на функции Низкая Когда условие сложное (3+ уровня) Разделение на функции
Упрощение условий Низкая Когда условие содержит много операторов Логические операторы
Замена магических чисел Низкая Когда число не имеет смысла Константы
Удаление пустых блоков Низкая Когда блок пуст Удаление pass
Разделение большой функции Средняя Когда функция > 50 строк Разделение на маленькие функции
Замена наследования на композицию Высокая Когда наследование сложное Использование объектов
Внедрение зависимостей Средняя Когда функция создаёт зависимости Передача параметров
Замена глобальных переменных Средняя Когда переменная используется в нескольких функциях Локальные переменные
Удаление неиспользуемого кода Низкая Когда код не используется Удаление старых функций

Как проводить рефакторинг безопасно: пошаговый алгоритм

Рефакторинг — это не просто изменение кода, это процесс, который требует подготовки и контроля. Вот пошаговый алгоритм, который поможет провести рефакторинг безопасно, основанный на моём многолетнем опыте.

Шаг 1: Подготовка к рефакторингу

  1. Определите цель. Чётко запишите, что вы хотите улучшить (например, читаемость, скорость, тестирование). Без чёткой цели легко уйти в бесконечный рефакторинг.
  2. Выберите участок кода. Не рефакторьте весь проект сразу. Выберите один модуль, функцию или класс. Я обычно начинаю с самого запутанного места, которое часто меняется.
  3. Создайте тесты. Если у вас нет тестов, создайте их. Тесты — это гарантия, что рефакторинг не изменит поведение программы. Напишите хотя бы дымовые тесты, покрывающие основные сценарии.
  4. Зафиксируйте текущее состояние. Сделайте коммит в Git с текущим кодом. Это позволит вернуться, если что-то пойдёт не так. Я всегда делаю коммит с сообщением «save point before refactoring».

Важно: Без тестов рефакторинг — это риск. Тесты — это ваша страховка. Если тестов нет, сначала создайте их, иначе вы не узнаете, что сломали логику.

Шаг 2: Применение техник рефакторинга

  1. Выберите технику. Начните с базовых техник (удаление дублирования, упрощение условий). Не пытайтесь исправить всё сразу.
  2. Измените код. Применяйте технику постепенно, по одному изменению. Например, сначала замените магические числа, потом упростите условие.
  3. Запускайте тесты. После каждого изменения запускайте тесты. Если тесты не проходят — верните изменение. Это сэкономит вам часы отладки.
  4. Коммитите изменения. После успешного изменения сделайте коммит в Git с понятным описанием, например «refactor: extract discount calculation».

Правило: Не делайте несколько изменений одновременно. Одно изменение — один коммит. Это позволяет откатывать точечно, а не всё сразу.

Шаг 3: Проверка и завершение

  1. Запустите все тесты. Проверьте, что все тесты проходят. В идеале должно быть полное покрытие модуля.
  2. Проверьте поведение программы. Запустите программу и убедитесь, что она работает как раньше, особенно в граничных случаях.
  3. Документируйте изменения. Запишите, что было изменено и почему. Это поможет другим разработчикам понять логику. Я часто добавляю краткое описание в коммит-сообщение и в README модуля.
  4. Сделайте финальный коммит. Зафиксируйте результат рефакторинга. Если рефакторинг был большим, можно объединить коммиты в один, но я предпочитаю оставлять историю.

Чек-лист: Безопасный рефакторинг

  • Определена цель рефакторинга
  • Выбран участок кода для рефакторинга
  • Созданы тесты (или обновлены существующие)
  • Сделан коммит текущего состояния (Git)
  • Применена техника рефакторинга (по одному изменению)
  • Запущены тесты после каждого изменения
  • Все тесты проходят
  • Проверено поведение программы
  • Документированы изменения
  • Сделан финальный коммит

Пример: В одном из моих проектов мы рефакторили модуль обработки заказов. Сначала мы создали тесты для каждой функции, затем применили технику «разделение большой функции», и после каждого изменения запускали тесты. В результате мы улучшили читаемость кода, не изменив его поведение. Сам процесс занял около 4 часов, но следующий фиче-реквест закрыли за полдня.

Типовые ошибки и как их избежать

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

Ошибка 1: Рефакторинг без тестов

Проблема: Без тестов вы не знаете, что рефакторинг не изменил поведение программы. Это как прыжок с парашютом без проверки снаряжения.

Как избежать:

  • Создайте тесты перед рефакторингом.
  • Запускайте тесты после каждого изменения.
  • Если тесты не проходят — верните изменение.

Важно: Тесты — это ваша страховка. Без них рефакторинг — это риск. Я не раз видел, как джуниоры меняли код без тестов и ломали продакшн.

Ошибка 2: Рефакторинг всего проекта сразу

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

Как избежать:

  • Рефакторьте один модуль, функцию или класс.
  • Применяйте техники постепенно, по одному изменению.
  • Сделайте коммит после каждого успешного изменения.

Правило: Одно изменение — один коммит. Не делайте несколько изменений одновременно. Это позволяет откатывать изменения точечно.

Ошибка 3: Рефакторинг без цели

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

Как избежать:

  • Чётко запишите цель рефакторинга (например, читаемость, скорость, тестирование).
  • Выберите участок кода, который соответствует цели.
  • Применяйте техники, которые решают эту цель.

Важно: Без цели рефакторинг — это просто изменение кода без пользы. Я всегда спрашиваю себя: «Что именно станет лучше после этого изменения?»

Ошибка 4: Рефакторинг с изменением логики

Проблема: Если вы изменяете логику программы, это не рефакторинг, а исправление багов или оптимизация. Это нарушает основное правило рефакторинга.

Как избежать:

  • Проверьте, что поведение программы не изменилось.
  • Запустите тесты и убедитесь, что они проходят.
  • Если тесты не проходят — верните изменение.

Правило: Рефакторинг не меняет логику программы. Он лишь улучшает способ её реализации. Если вам нужно изменить поведение, сначала напишите новый тест, затем реализуйте.

Ошибка 5: Рефакторинг без документирования

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

Как избежать:

  • Запишите, что было изменено и почему.
  • Используйте комментарии в коде для объяснения изменений (но не злоупотребляйте).
  • Обновите документацию проекта: README, ADR, вики.

Важно: Документация — это ключ к пониманию изменений. Без неё рефакторинг может стать проблемой. Я предпочитаю оставлять осмысленные коммит-сообщения и иногда рисовать диаграммы до и после.

Таблица: Ошибки и способы их избежать

Ошибка Причина Как избежать
Рефакторинг без тестов Нет гарантии, что поведение не изменилось Создать тесты, запускать после каждого изменения
Рефакторинг всего проекта сразу Огромный риск потери времени Рефакторить один модуль, применять техники постепенно
Рефакторинг без цели Бесполезное изменение кода Чётко записать цель, выбрать участок кода
Рефакторинг с изменением логики Это не рефакторинг, а исправление багов Проверить, что поведение не изменилось, запустить тесты
Рефакторинг без документирования Другие не поймут изменения Записать изменения, использовать комментарии, обновить документацию

Инструменты для рефакторинга: от IDE до автоматизации

Для успешного рефакторинга нужны правильные инструменты. Вот список инструментов, которые помогут вам провести рефакторинг эффективно.

1. IDE (Интегрированные среды разработки)

IDE — это основной инструмент для рефакторинга. Они предоставляют встроенные функции для автоматического рефакторинга, которые экономят время и снижают риск ошибок. Я всегда использую авторефакторинги, но обязательно проверяю результат.

Рекомендуемые IDE:

  • Visual Studio Code (для JavaScript, Python, TypeScript) — хорош с плагинами, такими как ESLint, Prettier.
  • IntelliJ IDEA (для Java, Kotlin) — мощные автоматические рефакторинги, включая выделение интерфейсов и замену наследования.
  • PyCharm (для Python) — мой фаворит для Python, отлично справляется с переименованиями, выделением функций и устранением дублирования.
  • Eclipse (для Java) — классика с богатым набором рефакторингов.
  • Xcode (для Swift, Objective-C) — обязателен для iOS-разработчиков.

Функции IDE для рефакторинга:

  • Автоматическое удаление дублирования: IDE может найти и объединить дублированный код.
  • Замена условий на функции: IDE может автоматически разделить сложные условия на функции.
  • Упрощение условий: IDE может упростить сложные условия.
  • Замена магических чисел на константы: IDE может найти и заменить магические числа на константы.
  • Разделение большой функции: IDE может автоматически разделить большую функцию на маленькие.

Важно: Используйте функции IDE для автоматического рефакторинга, но не забывайте проверять результаты. Автоматика не всегда понимает бизнес-логику и может предложить неудачное имя.

2. Инструменты автоматизации

Инструменты автоматизации помогают провести рефакторинг быстрее и с меньшими рисками, анализируя качество кода и выявляя проблемные места.

Рекомендуемые инструменты:

  • SonarQube (анализ качества кода, поиск дублирования, сложных условий) — внедряется в CI/CD и даёт обзор проблем. Я настраиваю его на каждом проекте, чтобы видеть технический долг.
  • ESLint (для JavaScript, проверка кода, поиск дублирования) — с правилами, запрещающими магические числа, избыточные условия и т.д.
  • Pylint (для Python, проверка кода, поиск дублирования) — отлично интегрируется с IDE и CI.
  • Checkstyle (для Java, проверка кода, поиск дублирования).
  • CodeClimate (анализ качества кода, поиск дублирования, сложных условий) — удобен для опенсорс-проектов.

Функции инструментов автоматизации:

  • Анализ качества кода: Инструменты могут найти проблемы в коде (дублирование, сложные условия, магические числа).
  • Поиск дублирования: Инструменты могут найти и предложить объединение дублированного кода.
  • Поиск сложных условий: Инструменты могут найти и упростить сложные условия.
  • Поиск магических чисел: Инструменты могут найти и заменить магические числа на константы.

Важно: Используйте инструменты автоматизации для анализа кода, но не забывайте проверять результаты. Они могут давать ложные срабатывания, особенно в нестандартных ситуациях.

3. Инструменты для тестирования

Тесты — это гарантия, что рефакторинг не изменил поведение программы. Вот инструменты для тестирования, которые я использую.

Рекомендуемые инструменты:

  • JUnit (для Java) — стандарт индустрии.
  • pytest (для Python) — простой и мощный, с фикстурами и параметризацией.
  • Jest (для JavaScript) — быстрый и удобный, с мокингом из коробки.
  • RSpec (для Ruby) — читаемый BDD-стиль.
  • PHPUnit (для PHP) — надёжный инструмент для PHP-проектов.

Функции инструментов тестирования:

  • Запуск тестов: Инструменты могут запускать тесты после каждого изменения.
  • Проверка результатов: Инструменты могут проверить, что тесты проходят.
  • Отчёт о результатах: Инструменты могут показать, какие тесты не прошли, и покрытие кода.

Важно: Используйте тесты для проверки рефакторинга. Без тестов рефакторинг — это риск. Я всегда начинаю с написания тестов, если их нет, даже если это занимает время.

4. Инструменты для документирования

Документация — это ключ к пониманию изменений. Вот инструменты для документирования, которые помогают оставить след.

Рекомендуемые инструменты:

  • Markdown (для создания документации) — простой и универсальный.
  • Swagger/OpenAPI (для API документации) — автоматическая генерация из кода.
  • Jira (для управления задачами и документированием изменений) — связывайте коммиты с задачами.
  • Confluence (для создания документации) — удобен для архитектурных решений.
  • GitLab Wiki (для создания документации в GitLab) — хранится в репозитории.

Функции инструментов документирования:

  • Создание документации: Инструменты могут создать документацию для изменений.
  • Обновление документации: Инструменты могут обновить документацию после изменений.
  • Поиск документации: Инструменты могут найти документацию для изменений.

Важно: Используйте документацию для объяснения изменений. Без неё рефакторинг может стать проблемой, особенно для новых разработчиков. Я предпочитаю писать комментарии в коде только для неочевидных вещей, а основную документацию держать в README или ADR.

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

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

Исходный код (проблемный)

def process_order(order):
    total = 0
    if order['items']:
        for item in order['items']:
            if item['price'] > 0:
                if item['quantity'] > 10:
                    total += item['price'] * item['quantity'] * 0.9
                elif item['quantity'] > 5:
                    total += item['price'] * item['quantity'] * 0.95
                else:
                    total += item['price'] * item['quantity']
            else:
                print("Invalid price")
    else:
        print("No items")
    if total > 100:
        total *= 0.9
    elif total > 50:
        total *= 0.95
    # Дублирующийся код для расчёта скидки в другом месте
    discount = 0
    if total > 100:
        discount = 0.1
    elif total > 50:
        discount = 0.05
    total = total * (1 - discount)
    return total

Проблемы:

  • Сложное условие (4 уровня вложенности).
  • Магические числа (10, 50, 100, 0.9, 0.95).
  • Большая функция (более 50 строк).
  • Дублирование кода (расчёт скидки повторяется дважды).

Рефакторинг (пошагово)

Шаг 1: Замена магических чисел на константы

QUANTITY_THRESHOLD_HIGH = 10
QUANTITY_THRESHOLD_MEDIUM = 5
DISCOUNT_HIGH_QUANTITY = 0.9
DISCOUNT_MEDIUM_QUANTITY = 0.95
ORDER_TOTAL_THRESHOLD_HIGH = 100
ORDER_TOTAL_THRESHOLD_MEDIUM = 50
DISCOUNT_HIGH_TOTAL = 0.9
DISCOUNT_MEDIUM_TOTAL = 0.95

def process_order(order):
    total = 0
    if order['items']:
        for item in order['items']:
            if item['price'] > 0:
                if item['quantity'] > QUANTITY_THRESHOLD_HIGH:
                    total += item['price'] * item['quantity'] * DISCOUNT_HIGH_QUANTITY
                elif item['quantity'] > QUANTITY_THRESHOLD_MEDIUM:
                    total += item['price'] * item['quantity'] * DISCOUNT_MEDIUM_QUANTITY
                else:
                    total += item['price'] * item['quantity']
            else:
                print("Invalid price")
    else:
        print("No items")
    if total > ORDER_TOTAL_THRESHOLD_HIGH:
        total *= DISCOUNT_HIGH_TOTAL
    elif total > ORDER_TOTAL_THRESHOLD_MEDIUM:
        total *= DISCOUNT_MEDIUM_TOTAL
    # Дублирующийся код для расчёта скидки в другом месте
    discount = 0
    if total > ORDER_TOTAL_THRESHOLD_HIGH:
        discount = 0.1
    elif total > ORDER_TOTAL_THRESHOLD_MEDIUM:
        discount = 0.05
    total = total * (1 - discount)
    return total

Шаг 2: Упрощение условий (замена на функции)

def is_valid_price(price):
    return price > 0

def get_quantity_discount(quantity):
    if quantity > QUANTITY_THRESHOLD_HIGH:
        return DISCOUNT_HIGH_QUANTITY
    elif quantity > QUANTITY_THRESHOLD_MEDIUM:
        return DISCOUNT_MEDIUM_QUANTITY
    else:
        return 1.0

def get_order_total_discount(total):
    if total > ORDER_TOTAL_THRESHOLD_HIGH:
        return DISCOUNT_HIGH_TOTAL
    elif total > ORDER_TOTAL_THRESHOLD_MEDIUM:
        return DISCOUNT_MEDIUM_TOTAL
    else:
        return 1.0

def process_order(order):
    total = 0
    if order['items']:
        for item in order['items']:
            if is_valid_price(item['price']):
                quantity_discount = get_quantity_discount(item['quantity'])
                total += item['price'] * item['quantity'] * quantity_discount
            else:
                print("Invalid price")
    else:
        print("No items")
    total *= get_order_total_discount(total)
    # Дублирование ещё остаётся
    discount = 1 - get_order_total_discount(total)
    total = total * (1 - discount)
    return total

Шаг 3: Разделение большой функции на маленькие

def calculate_item_total(item):
    if not is_valid_price(item['price']):
        return 0
    quantity_discount = get_quantity_discount(item['quantity'])
    return item['price'] * item['quantity'] * quantity_discount

def calculate_items_total(items):
    return sum(calculate_item_total(item) for item in items)

def apply_order_discount(total):
    return total * get_order_total_discount(total)

def process_order(order):
    if not order.get('items'):
        print("No items")
        return 0
    total = calculate_items_total(order['items'])
    total = apply_order_discount(total)
    # Дублирование всё ещё здесь
    discount = 1 - get_order_total_discount(total)
    total = total * (1 - discount)
    return total

Шаг 4: Удаление дублирования

Заметим, что дублирующийся код выполняет то же самое, что и apply_order_discount, но с вычитанием из 1. Это можно убрать, так как apply_order_discount уже возвращает цену со скидкой. Удалим лишние строки.

def process_order(order):
    if not order.get('items'):
        print("No items")
        return 0
    total = calculate_items_total(order['items'])
    total = apply_order_discount(total)
    return total

Результат рефакторинга

Исходный код:

  • Сложное условие (4 уровня).
  • Магические числа.
  • Большая функция.
  • Дублирование.

Рефакторинг:

  • Простые условия (функции).
  • Константы вместо магических чисел.
  • Маленькие функции, каждая с одной обязанностью.
  • Нет дублирования.

Преимущества:

  • Читаемость улучшена: теперь понятно, что делает каждый шаг.
  • Тестирование упрощено: можно тестировать каждую функцию отдельно (например, get_quantity_discount).
  • Код легче поддерживать: изменение порога скидки меняется в одном месте.
  • Нет дублирования: логика скидки на сумму заказа используется только в одном месте.

Важно: Этот пример показывает, как применять техники рефакторинга на практике. Вы можете использовать его в своих проектах. В реальности я бы ещё добавил обработку ошибок и логирование, но это уже за рамками рефакторинга.

FAQ: Часто задаваемые вопросы о рефакторинге

1. Что такое рефакторинг и чем он отличается от переписывания?

Рефакторинг — это изменение внутренней структуры кода без изменения его внешнего поведения. Переписывание — это полное удаление старого кода и создание нового. Рефакторинг сохраняет все функции, переписывание — нет. Переписывание часто выглядит заманчиво, но почти всегда недооценивается по срокам и ведёт к новым багам.

2. Когда нужно начинать рефакторинг?

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

3. Как проводить рефакторинг безопасно?

  1. Определите цель.
  2. Выберите участок кода.
  3. Создайте тесты.
  4. Сделайте коммит текущего состояния.
  5. Применяйте техники постепенно.
  6. Запускайте тесты после каждого изменения.
  7. Проверьте поведение программы.
  8. Документируйте изменения.
  9. Сделайте финальный коммит.

4. Какие техники рефакторинга самые эффективные?

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

5. Что делать, если тесты не проходят после рефакторинга?

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

6. Можно ли рефакторить код без тестов?

Без тестов рефакторинг — это рискованная авантюра. Тесты — это гарантия, что рефакторинг не изменил поведение программы. Без них вы не знаете, что сломали. Я всегда советую сначала написать хотя бы несколько тестов на основные сценарии. Если тестов нет, начните с них.

7. Как документировать изменения после рефакторинга?

  1. Запишите, что было изменено и почему (в коммит-сообщении или вики).
  2. Используйте комментарии в коде для объяснения неочевидных изменений.
  3. Обновите документацию проекта (README, ADR).
  4. Используйте инструменты для документирования (Markdown, Swagger, Jira, Confluence).

8. Какие инструменты лучше использовать для рефакторинга?

Лучшие инструменты зависят от языка, но в целом это:

  • IDE: Visual Studio Code, IntelliJ IDEA, PyCharm.
  • Инструменты автоматизации: SonarQube, ESLint, Pylint.
  • Инструменты для тестирования: JUnit, pytest, Jest.
  • Инструменты для документирования: Markdown, Swagger, Jira, Confluence.

9. Как избежать типовых ошибок при рефакторинге?

  1. Создайте тесты перед рефакторингом.
  2. Рефакторьте один модуль, не весь проект.
  3. Чётко запишите цель рефакторинга.
  4. Проверьте, что поведение программы не изменилось.
  5. Документируйте изменения.

10. Сколько времени занимает рефакторинг?

Время зависит от размера кода и сложности проблем. В среднем:

  • Базовые техники: 1–2 часа для небольшого модуля.
  • Продвинутые техники: 2–4 часа.
  • Полный рефакторинг сложного модуля: 4–8 часов, а иногда и несколько дней для легаси-кода. Но важно не торопиться и делать маленькими шагами.

Важно: Не делайте рефакторинг слишком быстро. Лучше делать его постепенно, по одному изменению, и постоянно проверять, что всё работает.

Заключение: рефакторинг как часть культуры разработки

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

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