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

А бывало ли у вас такое?...
А бывало ли у вас такое?...

Приветствую! Меня зовут Андрей Степанов, я CTO во fuse8. Мне интересно знакомиться с опытом коллег по цеху и делиться своим. В сфере я уже больше 20 лет. В этой статье – размышления и советы относительно практики рефакторинга больших продуктов.

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

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

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

Когда нужно проводить рефакторинг кода продукта

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

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

*ходит по краю*
*ходит по краю*

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

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

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

Либо своевременный регулярный рефакторинг, либо монструозный техдолг

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

Зачастую поток бизнесовых задач непрерывный, все эти задачи с высоким приоритетами, и на рефакторинг как будто нет времени.

*звуки недовольства*
*звуки недовольства*

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

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

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

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

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

Не надо так
Не надо так

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

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

Так может и нечего беспокоиться, если продукт можно сделать заново? Если возникает такая мысль, то давайте расскажем, почему так не работает.

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

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

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

Легко ли переписать продукт с нуля

Нет. Как минимум потому, что на это потребуется вдвое-втрое больше времени (и денег), чем ушло на первоначальную версию. В нашей практике был подобный случай.

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

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

Первая сложность – несколько команд. Нужна команда, которая отвечает за старый проект, и отдельная команда, которая будет разрабатывать новый проект. Нужно много людей. Работа этих людей стоит денег. А еще им нужно будет потратить время на онбординг, потому что даже при наличии «старичков» на проекте, вновь прибывшие не впитают знания о нем по щелчку. Кроме того, командам придется синхронизироваться на случай внесения изменений в проекты.

Вторая сложность – это миграция старых данных на новую систему. Структура базы полностью меняется: невозможно взять и запустить новую систему и работать только с новыми данными. Скорее всего, придется данные из предыдущей системы переместить в новую. Пока миграция не завершится, а данные со 100% вероятностью не будут перенесены корректно, старую систему придется поддерживать.

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

А еще придется полностью переписать весь процесс деплоя и заново настроить метрики, по которым можно будет отследить «живость» проекта, потому что на старте неминуемо будут возникать проблемы.

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

Что нужно, чтобы рефакторинг был проще и быстрее

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

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

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

Комментирование кода. Особенно комментарии важны в местах со сложной логикой. Иногда в код приходится вводить странности, вызванные не вредностью разработчика, а внешними обстоятельствами. Например, сервис может использовать какое-нибудь нестандартное API или найдутся какие-то нелогичные на первый взгляд бизнес-требования. Такие места в коде лучше отмечать комментариями.

Ну, и последнее – своевременность.

Гораздо проще потратить чуть больше времени в моменте, чем доводить продукт до состояния, когда рефакторинг займет вечность.

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

Тезисный итог

К чему приводит несвоевременный рефакторинг:

  • На переработку существующей функциональности целиком тратится намного больше времени, чем если бы она была своевременно переработана по кусочкам.

  • Становится страшно вносить правки в существующую функциональность: сложно оценить размер возможных поломок. Это типичная проблема legacy-проектов.

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

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

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

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

  • У системы постоянно падает производительность.

  • Если все совсем плохо, придется выбросить текущее решение и написать все сначала. А это боль.

Причины, по которым возникает необходимость рефакторинга:

  • Задача на этапе аналитики была проработана неверно.

  • Назначение функциональности со временем сильно поменялось.

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

  • Техническое задание было верным, но были допущены ошибки на этапе разработки. Например, была заложена излишняя универсальность. Либо была неправильно продумана архитектура.

  • Компетенции команды со временем улучшаются, поэтому становятся видны огрехи в ранее написанном коде.

  • Нагрузка на систему и (или) размер хранимых данных превосходят критическую отметку. Из-за этого падает производительность сервиса.

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

Комментарии (7)


  1. iv660
    22.06.2023 11:06
    +1

    При рефакторинге... производительность растет

    Правильно ли я понимаю, что речь здесь о производительности команды разработчиков? Ведь рост производительности кода в моем скромном понимании - задача не рефакторинга, но оптимизации.


  1. nronnie
    22.06.2023 11:06

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


    1. iv660
      22.06.2023 11:06

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

      Когда припрет - ну, например, когда код настолько макаронный, что новую фичу в него не вставить никак, - матерятся, но рефакторят.

      С другой стороны, знаю людей, которые и вполне сознательно рефакторят. В том числе в рамках подхода "Red - Green - Refactor".


    1. sepulkary
      22.06.2023 11:06
      +1

      Я, например, периодически делаю рефакторинг. Но, в отличие от общего посыла статьи, которая рекомендует готовиться к рефакторингу аки к Новому Году, я просто периодически рутинно правлю куски, которые кажутся мне плохочитаемыми. Такие правки, разумеется, оформляю отдельным коммитами.


      1. nronnie
        22.06.2023 11:06
        +1

        И под такие правки заводится отдельная задача в трекере, под неё выделяется время, она после этих правок проходит ревью и тестирование? Или речь идет о каких-то собственных пет-проектах?


        1. sepulkary
          22.06.2023 11:06

          Речь идёт о рабочих проектах.
          Отдельная задача - нет, не заводится. Да, время выделяется, ревью и тестирование тоже в общем порядке.

          А почему это вас это так удивляет? Это же Boy Scout Rule (упоминаемый, в частности, в "Чистой архитектуре") - всегда оставляй код немного чище, чем он был до твоего прихода. Если я вижу кусок кода, не удовлятворяющий, скажем, PEP8 - то да, я его чищу, и делаю это в рабочее время, за деньги :)


          1. nronnie
            22.06.2023 11:06

            Это все, конечно, правильно, но, таким образом можно разве только переменные переименовывать, да отступы выравнивать. Потому что если мне на ревью приходит задача из жыры: "Поменять там-то 42 на 24", то, честно говоря, мне не особо хочется обнаружить, что эта 42 при этом теперь еще и переехала в конфиги и передается оттуда через какой-то новый интерфейс, вставляемый через DI - даже будь это с моей точки зрения сто раз правильно. В свою очередь я тоже так делать не буду - я не то что рефакторинг, а даже кривое форматирование поправлять не стану, если это выходит за рамки конкретной задачи.