Ваша кодовая база — это хаос. Вы это знаете, и ваша команда тоже. У одной функции 47 параметров. Переменные названы в честь ваших кошек. И где-то там комментарий 2015 года гласит: «TODO: исправить это перед запуском в производство», и голос в нём звучит всё более отчаянно.

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

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

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

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

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

Проблема в том, что это чувство продуктивности — очень хороший лжец.

60% времени разработчика тратится на чтение кода, это верно. Но это не значит, что 60% времени разработчика должно уходить на улучшение кода. Существует огромная разница между «этот код трудно читать» и «этот код нужно немедленно рефакторить». Одно — наблюдение. Другое — религия, и она истощает вашу скорость разработки.

Позвольте мне описать картину, которую вы узнаете:

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

Эта «немного» превращается в часы. Эти часы превращаются в запрос на перенос с более чем 300 изменёнными строками. Обзор кода превращается в философскую дискуссию о том, должен ли код следовать шаблону A или шаблону B. Ваша функция отправляется с задержкой. Ваша команда разочарована. А фактическая бизнес-ценность, которую вы доставили? Она идентична той, которая была бы, если бы вы оставили старый код в покое.

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

Скрытые затраты, о которых никто не говорит

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

Давайте разберём, что на самом деле происходит во время проекта рефакторинга:

Вложения времени, которые никогда не заканчиваются

Рефакторинг обманчиво дорог. Вы не просто меняете код — вам нужно:

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

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

Колесо рулетке с ошибками

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

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

Альтернативные издержки: невидимый убийца

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

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

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

Когда рефакторинг действительно имеет смысл

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

Разница между «необходимым рефакторингом» и «ненужным рефакторингом» сводится к простому вопросу: препятствует ли это блок прогресса?

Реальные триггеры для рефакторинга

1. Код блокирует новые функции. Когда вы буквально не можете добавить нужную функцию без предварительного распутывания существующего кода, это сигнал для рефакторинга. Не потому, что код некрасивый, а потому, что он функционально препятствует прогрессу. 2. Производительность действительно проблематична. Не гипотетически проблематична. Не «мы должны оптимизировать когда-нибудь». Реальные пользователи испытывают реальную медлительность. Только тогда рефакторинг производительности оправдан. 3. Архитектура действительно вредна. Если ваш текущий дизайн делает невозможным добавление необходимой функциональности или вызывает частые ошибки на уровне продакшна, стоит рассмотреть архитектурный рефакторинг. 4. Вы обучаете новых членов команды, и они сталкиваются с трудностями. Но даже здесь — будьте осторожны. Иногда решение не в рефакторинге; оно в лучшей документации.

Что не является триггером:

  • «Этот код не соответствует нашему стилю» (мнение, а не препятствие);
  • «Мы могли бы использовать здесь шаблон проектирования» (приятно, но не обязательно);
  • «Эта функция слишком длинная» (имеет значение только если её трудно понять);
  • «Это могло бы быть более СУХИМ» (дублирование не всегда плохо);
  • «Я прочитал статью об этом лучшем подходе» (недавняя предвзятость, а не срочность).

Диаграмма, которая должна преследовать ваши сны

Вот реальное дерево решений, которому должен следовать ваш рефакторинг:

graph TD A["Блокирует ли этот код вашу способность выпускать функции или исправлять ошибки?"] -->|Нет| B["Остановитесь здесь. Переходите к чему-то ценному."] A -->|Да| C["Рефакторинг разблокирует эту проблему?"] C -->|Нет| D["Переписать или изменить архитектуру
Это отличается от рефакторинга"] C -->|Да| E["Сколько времени займёт рефакторинг?"] E -->|Меньше, чем работа над функцией, которую он разблокирует| F["Продолжайте рефакторинг"] E -->|Больше, чем работа над функцией, которую он разблокирует| B F --> G["Напишите тесты перед рефакторингом"] G --> H["Рефакторинг"] H --> I["Убедитесь, что ничего не сломалось"] I --> J["Выпускайте фактическую функцию сейчас"]

Заметили что-нибудь? Нет поля «потому что код некрасивый». Нет поля «потому что это не кажется правильным». Нет поля «потому что я хочу использовать мою новую любимую библиотеку».

Практический пример: рефакторинг, которого не было

Позвольте мне показать вам, как это выглядит на практике. Представьте, что у вас есть этот код:

def calculate_price(items, customer_type, discount_code):
    total = 0
    for item in items:
        price = item['price']
        quantity = item['quantity']
        item_total = price * quantity
        if customer_type == 'premium':
            item_total = item_total * 0.9
        if discount_code:
            if discount_code == 'SAVE10':
                item_total = item_total * 0.9
            elif discount_code == 'SAVE20':
                item_total = item_total * 0.8
        tax = item_total * 0.1
        item_total = item_total + tax
        total = total + item_total
    return total

Этот код немного ранит вашу душу.