Для множества команд рефакторинг — это боль. Потому что если ты занимаешься рефакторингом, то не разрабатываешь основной продукт, а если не занимаешься — растет технический долг проекта. В какой-то момент команды приходят к мысли: «Давайте разграничим рефакторинг и разработку и выделим на него, например, 20% наших человеко-часов, а остальное время продолжим заниматься разработкой!» Мысль эта неплохая, вот только дело в том, что на каждые 100 разработка-часов вы никогда не получите 20 чистых часа рефакторинга. Потому что «разработка» — это не только работа с кодом.

Если мы говорим о зрелых командах, а не сжатых в материальную точку мини-коллективах на 3-5 человек, то «разработка» включает в себя еще целую массу различных активностей команды кроме написания кода. В «разработку» можно записать так нелюбимые многими митинги, работу с документацией, ведение отчетности в таск-менеджерах и так далее. Все это съедает примерно 30% от наших часов, выделенных на разработку. И как-то незаметно у нас получается, что вместо картины «80 часов кодим, 20 часов рефакторим» мы получаем неприглядную цифру в ~13 часов на, непосредственно, сам рефакторинг, потому что все остальное было поглощено другими активностями.

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

Почему вообще рефакторинг вызывает столько проблем? В первую очередь по причине того, что команды не могут адекватно оценить собственные ресурсы и совместить этот процесс с разработкой. Людям кажется, что если их рабочий день составляет 8 часов в день (40 в неделю), то все 40 часов они, якобы, занимаются разработкой. Это не так.
Весь процесс разработки можно разделить на две части: это побочные активности и все, что связано непосредственно с кодом.


Вот как выглядит распределение времени одной из наших команд разработки

У читателя может возникнуть резонный вопрос: «а как вы построили эту диаграмму?» и сейчас мы постараемся дать ответ. Данные были взяты не с потолка, а на основе нашей внутренней статистики. Наши разработчики ведут учет своей активности в Jira. Каждый такой персональный ворклог состоит из четырех разделов: код-ревью, технические обсуждения, конкретные тикеты и «все остальное». Благодаря этой достаточно простой и прозрачной классификации мы и построили аналитику того, сколько времени в каких командах мы тратим на каждый аспект разработки. Если на встречи, стендапы и ревью выпадает треть всего рабочего времени — все в пределах нормы. Если времени на эти активности тратится больше 33% — то в команде есть проблемы и их надо решать.

Вроде бы, тут нет подвоха и все логично, но как нам вписать в эту историю рефакторинг? Объявить «месяц рефакторинга» и забить на разработку? Однако у любого коммерческого продукта есть свой график, выбиваться из которого крайне нежелательно. Еще рефакторинг похож на трясину: если начал им заниматься, то остановиться сложно, тебя тянет на это дно. Рефакторинг смещает фокус внимания команды на себя, и мы получаем какой-то чудовищный перекос в сторону «наведения порядка» в уже написанном коде вместо того, чтобы двигать проект в светлое будущее. Так как распределить время?

Немного ответов дает следующий слайд:


Ведите логи времени, это дико полезно

Встречи и ревью остаются нетронутыми, потому что мы считаем, что эти активности максимально оптимизированы и сведены к адекватному минимуму (те, кто работал в командах, где митинги и ревью кода занимали 70% времени, наши слова подтвердят). Значит, мы берем время на рефакторинг кода и баг-фиксы из разработки. И тут мы опять применяем подход «одной и двух третей» и делим полученные нами человеко-часы на «рефакторинг» и «баги», четко разделяя эти понятия. Такая модель является жизнеспособной и позволяет найти время на наведение порядка на проекте, хотя бы не увеличивая технический долг. Если мы откусим от часов «разработки» слишком много, то проект застопорится.

Подходим к процессу рефакторинга правильно


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

Лучшим решением кажется позволить тимлиду или другому руководителю разработки определить фронт работ, назначить таски и приступить к рефакторингу. Но у такого варианта есть серьезный изъян. Тимлид — это дирижер нашей команды, но дирижеру не всегда очевидны проблемы музыкантов на местах. Если вы сторонник жесткой вертикали и модели, в которой один человек будет решать, как будет проходить рефакторинг, то вы добровольно садитесь в горящий КаМАЗ, который со сломанными тормозами несется с горы в пропасть.

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

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


Те самые карточки

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

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

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

Расстановка приоритетов


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

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

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

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

Что мы получаем в итоге


С описанным подходом мы органично встраиваем процессы рефакторинга в текущую разработку, снижаем размер (ну или останавливаем рост) технического долга и все это без каких-либо крупных жертв.

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

Полезные материалы:

1. Доклад Алексея Катаева про рефакторинг: больше примеров из практики, а также удивительная история менеджера Глеба, который не хотел рефакторить, пока…



2. Наш регламент обсуждения задач

3. Наш чек-лист код-ревью

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


  1. reforms
    26.09.2019 16:27
    +1

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


    1. spasibo_kep
      26.09.2019 19:36

      Человек, которому не все равно и который умеет в баланс, кажется, вообще нигде борозды не испортит

      > Простите, накипело…

      А у себя как решаете, нашли такого человека?


      1. reforms
        27.09.2019 10:28

        >> А у себя как решаете, нашли такого человека?

        У нас стратегически другой подход: была сделана ставка на жесткий код-ревью. Это не значит 'взамен' рефакторингу. Но время на такой код-ревью существенно сокращает свободное.


        1. spasibo_kep
          27.09.2019 13:42

          Класс, а скажите, жесткий — это как? В чем выражается?

          У нас команды, например, к такому подходу пришли.


    1. VolCh
      27.09.2019 09:24

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


      1. spasibo_kep
        27.09.2019 13:43

        Привет, а пробовали обсуждать с командой и бизнесом, как это исправить? Чего говорят?


        1. VolCh
          27.09.2019 15:34

          "После "релиза" будем рефакторить". Справедливости ради рефакторим и сейчас, но по мелочи относительной.


  1. VolCh
    27.09.2019 09:30

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


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


    Встречались с такими проблемами?


    1. deitry
      27.09.2019 09:39

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


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


      1. VolCh
        27.09.2019 09:56

        но в целом стало гораздо читабельнее

        Это одна из возможных целей рефакторинга. Я бы сказал что такую задачу нужно формулировать как "Сделать код модуля N читабельней", а не "отрефакторить модуль N"


        1. deitry
          27.09.2019 10:33

          Можно было бы сформулировать, но исторически закрепилось такое название, никто не против.


          А ещё, если заявлять читабельность как непосредственную цель, то возникает вопрос, а что считать объективной, вычисляемой метрикой читабельности? Количество применяемых сущностей/методов на 100 условных строк модуля, глубина стека вызовов умноженная на разбросанность вызываемых методов по модулю, "плотность кода", что бы это ни значило, или количество людей, которые согласны, что стало лучше?


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


          1. VolCh
            27.09.2019 11:56

            Ну можно считать такой метрикой прохождение код-ревью у (всей?) команды. Вашими словами "количество людей, которые согласны, что стало лучше". (Человеко)читабельность ведь субъективная величина по определению.


            Я к тому, что у "отрефакторить модуль N" могут быть и другие цели, навскидку:


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

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


      1. G0ran
        27.09.2019 14:31

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

        а тесты были написаны?


        1. deitry
          27.09.2019 15:34

          Как ни странно, да.


          Добавлю деталей, чтобы пояснить на примере. Модуль слушает клавиатуру и в зависимости от нажатых клавиш формирует пакет с командами. Было реализовано с возможностью расширения вширь (если пакет станет больше или изменится его структура) и с возможностью динамической подмены биндингов (то бишь, чтобы те же команды отправлялись с других клавиш). И вот это было реализован через ж кучу отдельных списков, каждый из которых отвечал строго за свою функцию (список с используемыми кнопками, список с названиями команд, мапа с соответствием между командами и индексом числа в пакете...). Любой из этих списков можно было подменять в отдельности просто подставив новый, но этот новый нужно было проверить на соответствие по длине всем остальным спискам. После подстановок эти списки динамически превращались в одну волшебную мапу, с помощью которой конечная функция "нажми на кнопку — получишь результат" выглядела достаточно просто.


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


  1. HellWalk
    27.09.2019 14:43

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

    Если в компании первое и второе противопоставляется — нужно бежать из такой компании