Сравнение эффективности работы команд Проекта Э до и после реинжинирига
July 14, 2023
Введение
Мы закончили реинжиниринг Проекта Э, поработали три месяца только с новой кодовой базой и пришло время подводить итоги.
В изначальных критериях успеха реинжиниринга Проекта Э я целился в то, чтобы как минимум удвоить скорость разработки - лучше и объективно, и субъективно по мнению РП и заказчика. Но субъективные ощущения РП и заказчика не совпадают - РП видит увеличение скорости разработки, а заказчик - нет. Поэтому я перелопатил 517 задач в Jira, чтобы понять объективное состояние дел.
ТЛДР - по статистике количество трудозатрат и багов и правда удалось снизилось в 2 раза. Подробности исследования - далее в посте.
Методика оценки результатов по Jira
Оценка результатов затруднена целым спектром проблем:
- О разработке оригинальной версии данных практически нет. Есть только история гита, по которой можно понять размер команды (1 человек) и календарные сроки (9 месяцев - с 13 января - по 8 октября);
- Над проектом работали три разные команды - оригинальная, .net-команда у нас и kotlin-команда у нас;
- В Jira, откровенно говоря, у нас бардачок. Есть закрытые (и сделанные) задачи без трека. Есть небольшие задачи, в которые затрекано десятки часов.
Тем не менее я постарался сделать максимум для того, чтобы "сравнивать апельсины с апельсинами" и получить более-менее адекватный результат.
Для этого я навёл порядок в задачах и для каждой проставил/актуализировал:
- Тип — задача или баг;
- Компонент — .net- или kotlin-back;
- метка группировки;
- irrelevant — мусорные задачи вроде багов-не багов, которые решились в комментариях;
- admin — все возможные дейлики и прочая активность, не связанная с разработкой;
- groups — реализация функциональности групп пациентов в старом и новом бэке;
- dumps — реализация функциональности выгрузок в старом и новом бэке;
- reengineering — реализация функциональности, которая была в проекте на момент окончания разработки изначальной командой;
- misc — новые фичи в .net-беке, реинжиниринг этих фич в kotlin-беке, рефакторинг нового бэка (странных частей оригинального бэка, которые при реинжиниринге были сделаны как есть), новые фичи в kotlin-беке, всякая рабочая текучка;
- original-bug — баги оригинального бэка.
Эту разбивку задач можно визуализировать так:
Здесь цветами закодированы группы задач, которые я планирую сравнивать между собой, а серые группы — задачи, которые я исключил из сравнения.
Далее я планирую сравнивать:
- Реализацию групп в старом и новом бэке. Это лучшая пара, которая у меня есть - полностью идентичная функциональность, относительно крупная фича и по ней относительный порядок в Jira;
- Реализацию выгрузок. Эта фича хороша тем, что она большая и функциональность полностью идентичная. Однако операционные характеристики .net- и kotlin-решения сильно разные, поэтому сравнение не совсем адекватное;
- Реализация новых фич и текучка. Проблема этой пары заключается в том, что тут в задачах наибольший бардак с точки зрения трека (где-то его вообще нет - ушёл в соседнюю и т.п.). Но это и самая большая пара по объёму выборки, поэтому, надеюсь, она более-менее адекватно отразит общий тренд;
- Реализацию оригинальной версии и её реинжиниринг. Проблема этой пары заключается в том, что по разработке оригинальной версии у меня нет подробных данных, а есть только история гита. Однако по трудозатратам это самая большая пара, поэтому надеюсь что она тоже более-менее адекватно отразит общий тренд.
Данные
Реализация групп
Это небольшая фича по управлению собственно группами пациентов, наблюдаемых одним или более врачом. Не совсем тривиальная, из-за отношения many-to-many с относительно большой кардинальностью (до сотен строк).
- Старый бэк
- Общие трудозатраты: 104 часа
- Количество багов: 4
- Новый бэк
- Общие трудозатраты: 58
- Количество багов: 2
Здесь мы видим сокращение трудозатрат в 1.8 раз и сокращение багов в 2 раза, хотя по багам объём выборки не репрезентативен.
Реализация выгрузок
Это небольшая ридонли-админка пациентов и событий их дневников, в которой функционально всё стандартно - таблички на 10 столбцов, фильтрация по ним, сортировка, пагинация, выгрузка в xlsx. Все сложности были заключены в том, что данные лежат в разных БД. И в случае событий в перспективе ожидаются сотни миллионов строк.
- Старый бэк
- Общие трудозатраты: 247 часа
- Реализация: 179
- Исправление багов: 68
- Количество багов: 21
- Общие трудозатраты: 247 часа
- Новый бэк
- Общие трудозатраты: 175.75
- Реализация: 172
- Исправление багов: 3.75
- Количество багов: 4
- Общие трудозатраты: 175.75
Тут мы видим сокращение трудозатрат в 1.4 раза и сокращение количества багов в 5 раз.
Однако напрямую эти две реализации сравнивать нельзя, потому что здесь на новом бэке мы сделали выборки на потоковых join-ах. Благодаря чему новый бэк может обработать 1М строк (старый - 64К), а 64К строк сгенерировать за 11 секунд (старый - 165 секунд).
Кроме того, старый бэк полностью покрыт тестами.
Реализация новых фич и текучка
- Старый бэк
- Общие трудозатраты: 526 часа
- Реализация: 353.5
- Исправление багов: 172.5
- Количество задач: 14
- Количество багов: 22
- Медианные трудозатраты на задачу: 16
- Общие трудозатраты: 526 часа
- Новый бэк
- Общие трудозатраты: 497 часа
- Реализация: 426
- Исправление багов: 71
- Количество задач: 52
- Количество багов: 24
- Медианные трудозатраты на задачу: 5
- Общие трудозатраты: 497 часа
Сравнение этих метрик уже с большой натяжкой можно назвать объективным, потому что здесь у нас на входе по большей части разные задачи, выполненные разными людьми.
Тем не менее по всем метрикам наблюдается положительный тренд:
- За сопоставимый объём часов было выполнено в 3 раза больше задач;
- Относительное количество багов (22/14 vs 24/52) также уменьшилось в 3 раза;
- Наконец, медианные трудозатраты на задачу тоже снизились в три раза.
Можно сказать, что разница кроется в выборке и на новом беке в среднем делали в три раза более простые задачи. И, положа руку на сердце, исключать этого нельзя, потому что непонятно как объективно оценивать сложность задач.
Тем не менее я субъективно оцениваю, что в старом бэке была сделана только одна более-менее крупная задача (на 90 часов), а в остальном это были мелкие допилы и фиксы, которые занимали огромное количество времени.
Также субъективно, я оцениваю, что в kotlin-бэке было сделано четыре задачи, аналогичных по сложности самой большой задаче .net-бэка, плюс два крупных рефакторинга (100 и 13 часов).
Поэтому я склоняюсь к мнению, что этот блок можно считать подтверждением того, что разработка на новом бэке требует как минимум в два раза меньше трудозатрат и порождает как минимум в два раза меньше багов.
Реализация оригинальной версии и её реинжиниринг
- .net-бэк
- Оценочные общие трудозатраты: 1512
- kotlin-бэк
- Общие трудозатраты: 1162
- Реализация: 852
- Исправление багов: 59
- Административные задачи: 251
- Общие трудозатраты: 1162
Тут ускорение разработки составляет 1.3 раза. Однако здесь мы сравниваем наименее однородные вещи:
- У kotlin-команды было преимущество в фиксированном и проработанном "ТЗ". Однако "ТЗ" - это исходный и местами запутанный код на незнакомом языке;
- kotlin-бэк делали два юниора и стажёр, а .net-бек - один человек, и поэтому полагаю, что как минимум формально это был мидл или сеньёр;
- Оригинальному разработчику приходилось проектировать решение, а kotlin-команде приходилось подстраиваться под это решение, которое не всегда хорошо ложилось на наш стэк, а местами было очень странным;
- Оригинальный разработчик тесты не писал, а у kotlin-команды было 100% покрытие тестами хэппи пасов и 90% покрытие строк кода;
Итоги
Итак, у меня есть:
- Данные по полностью идентичной реализации одной и той же функциональности объёмом в 1-2 недели - почти в два раза быстрее и в два раза меньше багов;
- Данные по объективно более качественной реализации одной и той же функциональности объёмом в 1-1.5 месяца - в полтора раза быстрее и в пять раз меньше багов;
- Данные по 3 месяцам работы над преимущественно разными задачами - за примерно одинаковое время и с примерно одинаковым количеством багов, kotlin-команда сделала в три раза больше задач.
Исходя из этих данных я делаю следующий вывод - затратив 82% оригинальных трудозатрат, команда юниоров смогла создать базу проекта, который по самой консервативной оценке в два раза быстрее разрабатывать и содержит как минимум в два раза меньше багов.
Я считаю, это очень хороший результат и цель "как минимум двойное сокращение трудозатрат и багов" можно с уверенностью считать достигнутой. Но что позволило достичь этой цели?
Гипотезы причин улучшений
На итоговые цифры повлияли как минимум следующие факторы:
- Переход с микросервисов на монолит;
- Разные люди;
- Покрытие кода тестами;
- Переход с вертикальной на функциональную архитектуру;
- Разные стеки.
Как их расцепить и точно определить вклад каждого фактора я не знаю. Но попробую передать своё субъективное ощущение. Спойлер — список выше отсортирован по убыванию вклада.
Переход с микросервисов на монолит
На мой взгляд, наибольший вклад в увеличение скорости разработки внёс переход на монолит. Пусть он будет ответственен за 32% улучшения. Из цифр видно, что версию на монолите сделали на 20-30 процентов быстрее (смотря что на что делить). И я думаю, что это консервативная оценка - если бы kotlin-версию делал также один мидл, то он сделал бы в два раза/на 50% быстрее. По крайней мере, для себя я сделал вывод, что делать проекты до человеко/года на микросервисах как минимум в два раза дороже, чем на монолите.
Разные люди
Далее идёт самый сложный фактор на мой взгляд - люди. По моей оценке вклад смены команды в увеличение скорости разработки составляет 31%.
Про оригинального разработчика я не знаю ничего, но с учётом довольно небольшой разницы между оригинальными трудозатратами и трудозатратами на реинжиниринг, могу предположить, что квалификация и мотивация оригинального разработчика примерно соответствовала kotlin-команде (я помню, что предположил, что это был как минимум мидл, но там была и оговорка: "как минимум формально").
А вот с .net-командой я зафакапился тотально. У меня там были все - и юниор, и мидл, и сеньёр, и техлид. Все, кроме юниора, имели свой грейд чисто формально. Поэтому всех их (кроме юниора) я быстро уволил (от двух недель до двух месяцев) за низкую эффектиность работы.
Тут ещё можно поспекулировать на тему того, влияла ли сложность работы с микросервисами, без тестов и на вертикальной архитектуре на мотивацию или нет. Наверняка сказать невозможно, но я уверен, что повлияли. И если бы мы просто поменяли команду, то за два-три месяца пришли бы примерно к тем же проблемам с мотивацией.
Покрытие кода тестами
Теперь, наоборот, самый простой фактор — покрытие тестами. Его вклад в сокращение багов — 100%, на мой взгляд. Если бы kotlin-команада работала без тестов, то багов было бы столько же, сколько и у .net-команды.
Касательно увеличения скорости разработки, то по цифрам выходит, что вклад тестов составляет 15% - в .net-беке на исправление багов уходило 30%, а в kotlin - 15% (это в новых фичах и поддержке, а в выгрузках - вообще - 2%). Но исходя из гипотезы, что тесты влияют на мотивацию, а также из тех соображений, что баги несут очевидный и серьёзный репутационный (а иногда и материальный ущерб) - вклад покрытия тестами я оцениваю на том же уровне, что и переход на монолит и смену команды - 30%.
Переход с вертикальной на функциональную архитектуру
Теперь к смене вертикальной архитектуры на функциональную. Я думаю, что этот фактор именно с точки трудозатрат на кодирование имел небольшое влияние — в лучшем случае 7%. Зато вкупе с отсутствием тестов, он имел серьёзное влияние на количество багов — я не стал тут уже закапываться в статистику, но в .net-беке у нас не раз были баги из серии "Тут SQL поправили, а в соседней директории — забыли".
Кроме того, уверен, необходимость писать кучу шаблонного и бессмысленного кода также имела существенное негативное влияние на мотивацию.
Разные стеки
Если вы следите за цифрами, то уже знаете, что вклад смены стека я оцениваю в 0%. На мой взгляд, между Kotlin и C# никакой существенной разницы нет. И при прочих равных, что изначальная разработка на Kotlin, что реинжинриниг на C# дали бы те же самые результаты.
Итоги
По моей оценке вклад факторов в результат следующий:
- Переход с микросервисов на монолит - 32%;
- Разные люди - 31%;
- Покрытие кода тестами - 30%;
- Переход с вертикальной на функциональную архитектуру - 7%;
- Разные стеки - 0%.
При чём здесь Эргономичный подход?
Помимо вопроса "стоило ли оно того в целом", меня ещё интересует вопрос "стоило ли проводить реинжиниринг по Эргономичному подходу"? Данных, чтобы дать обоснованный ответ, у меня нет, так как сравнивать не с чем, но пофантазировать всё-таки хочется.
Что бы было, если бы мы делали реинжиниринг по мейнстримному подходу - с тестами на моках, Hibernate, пакетированием по техническим аспектам и в императивном стиле?
Сравнивать kotlin-бэк с гипотетический мейнстримным бэком я в том же формате, что и с .net-бэком.
Реализация групп
Я думаю, что использование Hibernate и тестов на моках, позволило бы сократить трудозатраты на 10-30% и, возможно, несущественно бы увеличило количество багов.
- Гипотетический мейнстримный бэк
- Оценочные общие трудозатраты: 41-52 часа (<58 часов факта ЭП-версии> - 10-30%)
- Оценочное количество багов: 2-3 штуки (<2 бага факта ЭП-верисии> + 0-1 шт.)
Реализация выгрузок
Реализация выгрузок миллионов строк на базе Hibernate наверняка привела бы к деградации потребления памяти и скорости работы. Поэтому для сохранения качества реализации, выгрузки пришлось в любом случае делать на JdbcTemplate-е. По крайней мере, я даже в работе по мейнстримному подходу сделал бы выгрузку точно так же.
А в силу того, что в реализации много "юнитов" и у них много зависимостей, тесты на моках и сами стоили бы дороже, и багов больше бы пропустили. И, как следствие, ещё больше увеличили бы общие трудозатраты. В итоге, я думаю, получилось бы +10% к трудозатратам на тесты и 30% на фикс багов.
- Гипотетический мейнстримный бэк
- Оценочные общие трудозатраты: 245.9
- Реализация: 189.2 (<172 часов факта ЭП-версии> + 10%)
- Исправление багов: 56.7 (30% от 189.2)
- Оценочное количество багов: 13 (с потолка)
- Оценочные общие трудозатраты: 245.9
Реализация новых фич и текучка
В эту категорию попадают уже в основном доработки существующей функциональности и рефакторинг. И тут (по идее) должен начать проявляться эффект от применения ЭП. С точки зрения сцепленности продового кода, негативные эффекты мейнстримного подхода ещё не успели бы проявиться. А вот в тестах - уже бы проявились в полный рост. В итоге, я полагаю, трудозатраты на реализацию бы выросли на 10-20% (на актуализацию моков), а трудозатраты на исправление багов, пропущенных тестами на моках, выросли бы до 20-25%.
- Гипотетический мейнстримный бэк
- Общие трудозатраты: 562.2-585.7 часа
- Реализация: 468.6 (<426 часов факта ЭП-версии> + 10%)
- Исправление багов: 93.7-117.1 (20-25% от 468.6)
- Количество задач: — не знаю, как хоть сколько-нибудь адекватно оценить и выровнять с общими трудозатратами
- Количество багов: — не знаю, как хоть сколько-нибудь адекватно оценить и выровнять с общими трудозатратами
- Медианные трудозатраты на задачу: 5.5-6 (5 + 10-20%)
- Общие трудозатраты: 562.2-585.7 часа
Реализация оригинальной версии и её реинжиниринг
При выполнении реинжиниринга, за счёт использования Hibernate трудозатраты на реализацию сократились бы процентов на 20 и ещё процентов на 10 за счёт тестов на моках. С другой стороны, трудозатраты на исправление багов удвоились бы за счёт багов, пропущенных тестами на моках. Наконец, административные трудозатраты не изменились бы.
- Гипотетический мейнстримный бэк
- Общие трудозатраты: 965.4
- Реализация: 596.4 (70% от 852 часов факта ЭП-версии)
- Исправление багов: 118 (59 часов факта ЭП-версии + 100%)
- Административные задачи: 251
- Общие трудозатраты: 965.4
Итого
Итого общие трудозатраты на "первые две версии" (реинжиниринг и 3 месяца саппорта) по ЭП составили 2039 часов. А оценочные общие трудозатраты на "первые две версии" по мейнстримному подходу составили бы 1814.5-1849.
То есть первый год разработки по ЭП будет примерно на 10% дороже.
Однако, как показывает моя практика, при разработке по мейнстримному подходу, трудо- и баго-ёмкость задач растёт очень быстро.
В случае же ЭП, предположительно, они будут расти намного медленнее.
Это я и собираюсь проверить - я надеюсь, Проект Э проживёт ещё хотя бы пару лет (все предпосылки для этого есть) и я смогу ещё хотя бы три-четыре раза с интервалом в 3-6 месяцев повторить это упражнение и оценить тренд роста трудозатрат и количества багов на задачу при работе с эргономичной кодовой базой.
Выводы
Итак: стоило ли делать реинжиниринг? Безусловно да, на основе данных из Jira можно с уверенностью утверждать, что мы смогли снизить трудозатраты и количество багов как минимум в два раза. Это улучшение ещё "усугубляется" за счёт того, что для заказчика внешние рейты штатных kotlin-истов юниоров ниже внешних рейтов .net-чиков аутстафферов.
Стоило ли делать реинжинирнг по Эргономичному подходу? Доподлинно неизвестно. Гипотетически, при условии, что работы продолжатся ещё хотя бы год, и если я прав, что показатели будут деградировать заметно медленнее, чем по мейнстримному подходу - да. Но это всё теория, которую ещё предстоит проверить на практике.
Кроме того, результаты анализа дают дополнительное подтверждение следующим утверждениям:
- Первый год разработки на микросервисах дороже разработки на монолите. Минимум на 30%;
- Автоматизация тестирования снижает количество багов и трудозатрат на их устранение. Минимум в два раза;
- Мотивация команды имеет огромное влияние на трудозатраты. От 30% дополнительных трудозатрат в случае низкой мотивации.