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

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

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

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

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

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

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

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

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

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

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

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

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

Временные решения против рефакторинга

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

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

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

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

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

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

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

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

Такие решения и нужны продукту.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • На переработку существующей функциональности целиком тратится намного больше времени, чем если бы она была своевременно переработана по кусочкам.
  • Становится страшно вносить правки в существующую функциональность: сложно оценить размер возможных поломок. Это типичная проблема legacy-проектов.
  • При доработке функциональности баги постоянно доходят до продакшена, нет возможности оценить все риски.
  • Невозможно самостоятельно восстановить алгоритм работы определенной фичи. Для понимания работы приходится обращаться к документации по проекту, к аналитикам и другим разработчикам, отвлекая их от своих задач.
  • Тяжелый онбординг новичков: они не смогут самостоятельно разобраться с кодовой базой. Придется постоянно советоваться с коллегами, но и это не гарантирует отсутствие дефектов в работе.
  • Знания обо всех тонкостях работы проекта содержатся в головах разработчиков, поэтому расставаться с разработчиками очень страшно.
  • У системы постоянно падает производительность.
  • Если все совсем плохо, придется выбросить текущее решение и написать все сначала. А это боль.

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

  • Задача на этапе аналитики была проработана неверно.
  • Назначение функциональности со временем сильно поменялось.
  • Готовая функциональность постоянно дорабатывалась/расширялась, что привело к разрастанию и усложнению кода, появлению костылей.
  • Техническое задание было верным, но были допущены ошибки на этапе разработки. Например, была заложена излишняя универсальность. Либо была неправильно продумана архитектура.
  • Компетенции команды со временем улучшаются, поэтому становятся видны огрехи в ранее написанном коде.
  • Нагрузка на систему и (или) размер хранимых данных превосходят критическую отметку. Из-за этого падает производительность сервиса.
  • Необходимость в своевременном обновлении библиотек и фреймворка, чтобы исключить критические уязвимости безопасности и использовать современные библиотеки. Просто обновив определенную библиотеку или фреймворк можно улучшить производительность системы без необходимости вносить существенные изменения в код
3131
6 комментариев

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

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

3

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

1

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

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

1

Кучу денег на продукт может не потратите, но зато потратите на другое

ну и классно же, деньги они для того чтобы их тратить :)