В реальных проектах, особенно когда команда растёт, а сроки сжимаются, накопленный «грязный» код становится бомбой замедленного действия. Многие разработчики в такой ситуации паникуют и предлагают: «Давайте просто перепишем всё с нуля». Это ошибка, которая может стоить проекту месяцев работы и даже привести к его краху. Рефакторинг — это не переписывание, а системное улучшение структуры кода без изменения его внешнего поведения. Он делает код чище, понятнее и легче в поддержке, сохраняя при этом все функции, которые он выполняет.
Я прошёл путь от новичка, который часто писал «как получится», до тимлида и архитектора. Мой опыт показывает: регулярный рефакторинг — это не роскошь, а необходимость. В этой статье я подробно разберу, что такое рефакторинг, зачем он нужен, какие техники работают лучше всего, как проводить его безопасно и какие ошибки чаще всего совершают джуниоры. Вы получите практические чек-листы, примеры кода и пошаговые инструкции, которые сможете применить в своих проектах сразу после чтения.
Что такое рефакторинг и почему он критически важен
Рефакторинг (Refactoring) — это процесс изменения внутренней структуры кода для улучшения его читаемости, поддерживаемости и расширяемости без изменения его внешнего поведения. Если вы запускали программу до рефакторинга и после него, и она выдавала одинаковые результаты, значит, рефакторинг выполнен правильно.
Часто рефакторинг путают с:
- Оптимизацией — изменением кода для повышения скорости работы или снижения потребления ресурсов. Рефакторинг может косвенно улучшить производительность, но его главная цель — не скорость, а качество кода.
- Переписыванием (Rewriting) — полным удалением старого кода и созданием нового. Это радикальный шаг, который требует огромных ресурсов и часто приводит к потере функционала.
- Исправлением багов — устранением ошибок в логике программы. Рефакторинг не меняет логику, он лишь улучшает способ её реализации.
Почему рефакторинг важен для разработчика и проекта
Вот ключевые причины, почему рефакторинг должен быть частью ежедневной работы разработчика:
- Снижение стоимости поддержки. Код, который легко читать и изменять, требует меньше времени на исправление ошибок и добавление новых функций. В долгосрочной перспективе это экономит огромные деньги. По моим наблюдениям, команда, которая рефакторит регулярно, тратит на фичи в среднем на 30% меньше времени уже через пару спринтов.
- Увеличение скорости разработки. Когда структура кода понятна, новые разработчики быстрее встраиваются в проект, а старые — быстрее находят нужные места для изменений.
- Предотвращение накопления «грязного кода». Без рефакторинга проект быстро превращается в «лапшу» (spaghetti code), где каждая новая функция усложняет систему ещё сильнее. Это особенно заметно в монолитах, которые живут больше трёх лет.
- Поддержка E-E-A-T (Experience, Expertise, Authoritativeness, Trustworthiness). Качественный код демонстрирует опыт и экспертность разработчика, что важно для карьерного роста и доверия в команде.
- Упрощение тестирования. Чистый код проще тестировать, так как каждая функция имеет ясные границы и не зависит от посторонних частей. Модульные тесты пишутся быстрее, а интеграционные — реже ломаются.
Пример из жизни: В одном из моих проектов мы добавляли новую функцию к модулю, который уже работал 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: Подготовка к рефакторингу
- Определите цель. Чётко запишите, что вы хотите улучшить (например, читаемость, скорость, тестирование). Без чёткой цели легко уйти в бесконечный рефакторинг.
- Выберите участок кода. Не рефакторьте весь проект сразу. Выберите один модуль, функцию или класс. Я обычно начинаю с самого запутанного места, которое часто меняется.
- Создайте тесты. Если у вас нет тестов, создайте их. Тесты — это гарантия, что рефакторинг не изменит поведение программы. Напишите хотя бы дымовые тесты, покрывающие основные сценарии.
- Зафиксируйте текущее состояние. Сделайте коммит в Git с текущим кодом. Это позволит вернуться, если что-то пойдёт не так. Я всегда делаю коммит с сообщением «save point before refactoring».
Важно: Без тестов рефакторинг — это риск. Тесты — это ваша страховка. Если тестов нет, сначала создайте их, иначе вы не узнаете, что сломали логику.
Шаг 2: Применение техник рефакторинга
- Выберите технику. Начните с базовых техник (удаление дублирования, упрощение условий). Не пытайтесь исправить всё сразу.
- Измените код. Применяйте технику постепенно, по одному изменению. Например, сначала замените магические числа, потом упростите условие.
- Запускайте тесты. После каждого изменения запускайте тесты. Если тесты не проходят — верните изменение. Это сэкономит вам часы отладки.
- Коммитите изменения. После успешного изменения сделайте коммит в Git с понятным описанием, например «refactor: extract discount calculation».
Правило: Не делайте несколько изменений одновременно. Одно изменение — один коммит. Это позволяет откатывать точечно, а не всё сразу.
Шаг 3: Проверка и завершение
- Запустите все тесты. Проверьте, что все тесты проходят. В идеале должно быть полное покрытие модуля.
- Проверьте поведение программы. Запустите программу и убедитесь, что она работает как раньше, особенно в граничных случаях.
- Документируйте изменения. Запишите, что было изменено и почему. Это поможет другим разработчикам понять логику. Я часто добавляю краткое описание в коммит-сообщение и в README модуля.
- Сделайте финальный коммит. Зафиксируйте результат рефакторинга. Если рефакторинг был большим, можно объединить коммиты в один, но я предпочитаю оставлять историю.
Чек-лист: Безопасный рефакторинг
- Определена цель рефакторинга
- Выбран участок кода для рефакторинга
- Созданы тесты (или обновлены существующие)
- Сделан коммит текущего состояния (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. Как проводить рефакторинг безопасно?
- Определите цель.
- Выберите участок кода.
- Создайте тесты.
- Сделайте коммит текущего состояния.
- Применяйте техники постепенно.
- Запускайте тесты после каждого изменения.
- Проверьте поведение программы.
- Документируйте изменения.
- Сделайте финальный коммит.
4. Какие техники рефакторинга самые эффективные?
Самые эффективные техники — те, которые вы можете применять регулярно без больших затрат времени: удаление дублирования, замена условий на функции, упрощение условий, замена магических чисел на константы, разделение большой функции. Они дают быстрый выигрыш в читаемости и поддерживаемости.
5. Что делать, если тесты не проходят после рефакторинга?
Если тесты не проходят, немедленно верните изменение (откатите коммит). Проверьте, что вы не изменили логику программы. Создайте новые тесты, если нужно, и повторите рефакторинг более осторожно. Никогда не игнорируйте упавшие тесты — это верный признак, что вы что-то сломали.
6. Можно ли рефакторить код без тестов?
Без тестов рефакторинг — это рискованная авантюра. Тесты — это гарантия, что рефакторинг не изменил поведение программы. Без них вы не знаете, что сломали. Я всегда советую сначала написать хотя бы несколько тестов на основные сценарии. Если тестов нет, начните с них.
7. Как документировать изменения после рефакторинга?
- Запишите, что было изменено и почему (в коммит-сообщении или вики).
- Используйте комментарии в коде для объяснения неочевидных изменений.
- Обновите документацию проекта (README, ADR).
- Используйте инструменты для документирования (Markdown, Swagger, Jira, Confluence).
8. Какие инструменты лучше использовать для рефакторинга?
Лучшие инструменты зависят от языка, но в целом это:
- IDE: Visual Studio Code, IntelliJ IDEA, PyCharm.
- Инструменты автоматизации: SonarQube, ESLint, Pylint.
- Инструменты для тестирования: JUnit, pytest, Jest.
- Инструменты для документирования: Markdown, Swagger, Jira, Confluence.
9. Как избежать типовых ошибок при рефакторинге?
- Создайте тесты перед рефакторингом.
- Рефакторьте один модуль, не весь проект.
- Чётко запишите цель рефакторинга.
- Проверьте, что поведение программы не изменилось.
- Документируйте изменения.
10. Сколько времени занимает рефакторинг?
Время зависит от размера кода и сложности проблем. В среднем:
- Базовые техники: 1–2 часа для небольшого модуля.
- Продвинутые техники: 2–4 часа.
- Полный рефакторинг сложного модуля: 4–8 часов, а иногда и несколько дней для легаси-кода. Но важно не торопиться и делать маленькими шагами.
Важно: Не делайте рефакторинг слишком быстро. Лучше делать его постепенно, по одному изменению, и постоянно проверять, что всё работает.
Заключение: рефакторинг как часть культуры разработки
Рефакторинг — это не просто изменение кода, это часть культуры разработки. Он помогает команде двигаться быстрее, снижает количество багов и делает проект более устойчивым к изменениям. По моему опыту, проекты, где рефакторинг является регулярной практикой, живут дольше и приносят меньше головной боли.
Не ждите, пока код превратится в монстра. Внедряйте рефакторинг в ежедневный рабочий процесс: выделяйте немного времени в каждом спринте, договаривайтесь с командой о правилах чистого кода и используйте автоматические инструменты. Начните с малого — выберите один модуль, примените пару техник из этой статьи, и вы увидите, как код становится понятнее и приятнее в работе. Это инвестиция, которая окупается многократно.
