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

Мы записали историю от Алексея Катаева, руководившего разработкой интерактивной платформы для обучения английскому - основного продукта Skyeng. Эту платформу давным-давно начали писать еще основатели на JQuery и пятом PHP, а сейчас она живет на четвертом Symfony и восьмом Angular. За все это время ее ни разу не переписывали с нуля.

В закладки
Основано на реальных событиях

— Леша! Давай перепишем все с нуля! Здесь уже отрефакторить ничего нельзя! — С таким предложением ко мне приходят Дима и Катя, это мои разработчики.

— Ну так а что ж вы не рефакторили все это время?

— Это все Глеб. Глеб заставляет нас каждый день пилить фичи и говорит, что на рефакторинг нет времени.

И, естественно, предложение Димы и Кати отклоняется. Но проблема остается!

Видеоверсия истории для тех, кто больше любит слушать, чем читать

Глава первая, где мы договариваемся с Глебом

Мы вместе с Димой и Катей отправляемся говорить с Глебом. Поскольку у нас в Skyeng разработчики разбросаны по всему миру и работают удаленно, встречаемся мы в Hangouts.

— Глеб! — Говорят Дима и Катя. — Если мы будем и дальше быстро пилить фичи и не будем рефакторить, у нас будет плохой код!

— Ну и что, что плохой? Зато он приносит деньги, — парирует Глеб.

Не получилось. Ладно, пробуем зайти с другой стороны:

— Глеб! Если мы будем быстро катить фичи, то в конце концов наш проект ждет катастрофа!

— Ооо, быстро катить фичи? — Оживляется Глеб. — Да! Я как раз хотел на этой неделе проверить 10 гипотез; они должны принести много денег.

Вот такой у нас Глеб, все у него только деньги на уме. За это его и ценят, собственно. Коли так, попробуем задеть его за живое:

— Глеб! Если мы не будем рефакторить, мы не сможем быстро проверять гипотезы и впиливать твои прекрасные новые фичи, должные принести много денег. Если мы не будем рефакторить, стабильность нашего проекта упадет, у нас будет куча багов, мы начнем терять деньги. Глеб! Скоро мы не сможем нанимать классных разработчиков, мы будем тратить на их поиск кучу денег, а они будут приходить и говорить “да не хочу я работать с вашим плесневелым легаси!”. А если уйдут Дима и Катя, это вообще будет катастрофа.

Но это не все: перед тем, как мы пошли к Глебу, я дал Диме и Кате почитать книгу “Договориться можно обо всем!” Гэвина Кеннеди. Они знают, что мало просто напугать Глеба, надо выдвинуть конкретное предложение:

— Глеб! Давай будем тратить на рефакторинг 20% времени! Только в этом квартале, а в следующем, когда разберемся с основными проблемами, - всего 15%! А взамен мы предлагаем тебе Изолированные Костыли™!

— Окей, — наконец говорит Глеб. — 20% мы можем себе позволить, особенно если благодаря этому мы не будем терять деньги.

Почему у нас получилось?

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

Глава вторая, где мы учимся рефакторить без отрыва от выкатывания фич

— Ну что, Дима и Катя, теперь у нас 20% времени на рефакторинг!

Но Дима и Катя почему-то в задумчивости:

— А что же мы будем делать с фичами, которые не докатились, что же делать с критичными багами, которые срочно нужно фиксить?..

Уходят думать и возвращаются с предложениями:

— Давайте откусим 20% от квартала одним блоком и будем в это время сфокусированно рефакторить, — предлагает Дима.

— Погоди, Глеб ни за что на это не пойдет. Он скажет: “Как мы можем остановить производство наших фич и проверку гипотез?” А вариант отложить это на конец квартала не сработает, там всегда найдется, что делать, плюс времени, конечно, не останется.

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

— Катя! Тогда мы будем рефакторить только какие-то мелочи и не сможем сделать крупный рефакторинг. Кроме того, если мы будем говорить Глебу, что у этой задачи time to market 16 часов, а не 10, мы будем обманывать Глеба. Вот тебе, Катя, книга Сэма Харриса “Ложь”, в ней говорится о том, что в долгосрочной перспективе говорить правду всегда выгоднее. Твой вариант тоже не катит!

Что же остается?

Остается рефакторить параллельно с разработкой. И для сначала понять

Сколько времени выделять на рефакторинг

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

Нет.

Статистика собрана с реальной команды

Не все время разработчика уходит на, собственно, программирование. Поскольку у нас есть ворклоги, мы можем точно сказать, что примерно треть тратится на общение, ревью, стендапы и прочую подобную муть, и лишь две трети - на саму разработку. При этом разработка - не только работа над продуктом, это также фиксы багов и, собственно, рефакторинг. В результате получается, что теперь мы на рефакторинг можем выделить 13 часов из ста, это и будут те самые 20%, обещанные Глебу.

Итак, Катя и Дима взяли задачу, рефакторили честные 13 часов в начале месяца, а потом отправились привычно тестировать гипотезы и выкатывать фичи. Наступил конец месяца, и что? Как всегда, что-то где-то зафакапилось, приходит Глеб:

— Что-то вы много рефакторите, не успеваете работать над продуктом, давайте отменять.

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

Команда рефакторила 31% времени. Что скажет Глеб?)

Окей, мы определились, когда рефакторить. Теперь надо решить,

Что именно рефакторить

Катя и Дима предлагали нам переписать все с нуля.

— Нет, ребят, переписывать с нуля мы не будем.

— Почему?

— А потому, что у нас есть принцип 2.15:

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

— Мы будем бороться с техническим долгом.

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

— Нет, это нам не подходит. Мы тогда вообще не будем видеть света в конце тоннеля, ведь у нас бесконечное число проблем.

— Ну давай ты составишь нам список самых критичных, будем работать с ними!

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

Делаем доску в Трелло и говорим:

— Народ, пусть каждый напишет сюда карточки с проблемами, которые видел: костыли, кривые решения, старые библиотеки, старые API и т.д.

Когда я впервые провел такое в команде биллинга, то получил примерно такие карточки:

Что это и как с этим жить?

Ввели соглашение писать карточки в определенном формате:

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

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

Получаем сотню тикетов, но глупо же начинать их разрешать по очереди. Надо как-то приоритезировать. И здесь нам опять помогут разработчики: Дима с Катей лучше всех знают, где у них максимально болит. Проводим среди них опрос, где просим оценить каждый тикет по шкале от 1 до 5: 5 = нужно фиксить прямо сейчас, 1 = когда-нибудь в следующей жизни.

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

Итак, нам потребовалось:

  • Понять, что рефакторить надо параллельно. Раз в квартал или обманом (завышением оценки) - плохие варианты;
  • Прежде чем бороться с проблемой, ее надо формализовать;
  • Приоритезировать: не прыгать на коня и скакать шашка наголо, а сперва найти самые больные места.

Глава третья, где мы учимся делать так, чтобы больше никогда не рефакторить

Опять пришли Дима с Катей:

— Леша! Кажется, мы попали в какой-то бесконечный водоворот. Мы пилим фичи - рефакторим, пилим фичи - рефакторим, и нет из этого никакого выхода.

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

Пицца-клуб. Совершенно элитный закрытый vip-клуб, члены которого должны прочитать книгу, сделать по ней презентацию на пять минут для своей команды и за это получить пиццу с доставкой на дом. Филонишь? Тебя ждет страшное наказание: исключение из vip-клуба!

Education Day. В советских НИИ это называлось “библиотечным днем”: выделяем разработчику целый оплаченный день, даем список статей (или выбирает сам), он их читает и по итогам публикует обзор - что получил, чему обучился, рекомендует ли команде. Эдакий полу-выходной. Наш опыт говорит, что это действительно работает.

Таблички. Я люблю таблички.

— Здравствуй, команда, напиши мне в этой табличке, насколько каждый из вас разбирается в приеме платежей, поиске сотрудников и расчете зарплат:

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

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

Боты. Мы автоматизировали процесс технического ревью. У нас есть бот Джейк: каждое утро он собирает задачи на разработку и проводит опрос “Кто хочет обсуждать вот эту задачу”. Голосуют “за” те, кто шарит в задаче, разбирается в теме, либо им просто очень интересно поучаствовать. Количество участников ограничено четырьмя, потом формируется расписание и проводятся встречи.

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

Эпилог

Итак, Дима с Катей счастливы, потому что мы:

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

Можно подумать, что теперь наш проект на 100% идеален. Но на деле, всегда есть место техническому долгу. Однако, теперь мы о нем в курсе, потому что на него у нас есть карточка, и мы полностью контролируем ситуацию. Когда-нибудь доберемся и до него.

Материал опубликован пользователем.
Нажмите кнопку «Написать», чтобы поделиться мнением или рассказать о своём проекте.

Написать
{ "author_name": "Alexey Korneev", "author_type": "self", "tags": [], "comments": 0, "likes": 3, "favorites": 5, "is_advertisement": false, "subsite_label": "dev", "id": 81030, "is_wide": true, "is_ugc": true, "date": "Thu, 29 Aug 2019 17:08:03 +0300", "is_special": false }
0
{ "id": 81030, "author_id": 12744, "diff_limit": 1000, "urls": {"diff":"\/comments\/81030\/get","add":"\/comments\/81030\/add","edit":"\/comments\/edit","remove":"\/admin\/comments\/remove","pin":"\/admin\/comments\/pin","get4edit":"\/comments\/get4edit","complain":"\/comments\/complain","load_more":"\/comments\/loading\/81030"}, "attach_limit": 2, "max_comment_text_length": 5000, "subsite_id": 235819, "last_count_and_date": null }
Комментариев нет
Популярные
По порядку
{ "page_type": "article" }

Прямой эфир

[ { "id": 1, "label": "100%×150_Branding_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox_method": "createAdaptive", "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfl" } } }, { "id": 2, "label": "1200х400", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfn" } } }, { "id": 3, "label": "240х200 _ТГБ_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fizc" } } }, { "id": 4, "label": "Article Branding", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "p1": "cfovx", "p2": "glug" } } }, { "id": 5, "label": "300x500_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfk" } } }, { "id": 6, "label": "1180х250_Interpool_баннер над комментариями_Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "ffyh" } } }, { "id": 7, "label": "Article Footer 100%_desktop_mobile", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjxb" } } }, { "id": 8, "label": "Fullscreen Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjoh" } } }, { "id": 9, "label": "Fullscreen Mobile", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjog" } } }, { "id": 10, "disable": true, "label": "Native Partner Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyb" } } }, { "id": 11, "disable": true, "label": "Native Partner Mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyc" } } }, { "id": 12, "label": "Кнопка в шапке", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "p1": "bscsh", "p2": "fdhx" } } }, { "id": 13, "label": "DM InPage Video PartnerCode", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox_method": "createAdaptive", "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "flvn" } } }, { "id": 14, "label": "Yandex context video banner", "provider": "yandex", "yandex": { "block_id": "VI-223676-0", "render_to": "inpage_VI-223676-0-1104503429", "adfox_url": "//ads.adfox.ru/228129/getCode?pp=h&ps=bugf&p2=fpjw&puid1=&puid2=&puid3=&puid4=&puid8=&puid9=&puid10=&puid21=&puid22=&puid31=&puid32=&puid33=&fmt=1&dl={REFERER}&pr=" } }, { "id": 15, "label": "Баннер в ленте на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byudx", "p2": "ftjf" } } }, { "id": 16, "label": "Кнопка в шапке мобайл", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byzqf", "p2": "ftwx" } } }, { "id": 17, "label": "Stratum Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvb" } } }, { "id": 18, "label": "Stratum Mobile", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvc" } } }, { "id": 19, "disable": true, "label": "Тизер на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "p1": "cbltd", "p2": "gazs" } } } ] { "page_type": "default" }