Как не продолбать архитектуру в погоне за фичами
Я работаю в Miro со дня основания, вначале как фронтенд инженер, сейчас как менеджер core-команд, которые разрабатывают внутреннее ядро канваса и realtime-коллаборации на нём.
Мы очень быстро растём: в пользователях, в размере команды, в количестве выпускаемых фич. Немного фактов за 2020 для контекста:
Перешагнули рубеж в 10 миллионов регистраций;
Пиковая онлайн-нагрузка за год выросла в 7 раз;
Команда разработки выросла в 2 раза (инженеры, продакты, дизайнеры);
Выглядит круто! Но есть нюансы.
Наблюдая за компанией 9 лет, я вижу, что кратное увеличение количества инженеров приводит к падению скорости разработки. Задачи по созданию новой функциональности приводят к костылям. Текущая архитектура не позволяет сделать их правильно, а на рефакторинг времени не хватает. Непонятно кто именно должен брать на себя рефакторинг. Начинаемые архитектурные изменения не доводятся до конца из-за отсутствия фокуса. Порог входа в код повышается, онбордить новичков становится сложнее. Time to market новых фичей падает.
Это то, куда может завести разработка с фокусом на новую функциональность, но без выделения достаточного времени на архитектуру. На стадии роста новые фичи часто побеждают рефакторинг по приоритетам.
Так мы сталкиваемся с задачей «Как сохранить скорость разработки фичей и гибкость архитектуры на стадии роста?». Откатимся назад в прошлое и будем пробовать разные подходы чтобы прийти к тому, что есть сейчас.
Выделим 20% времени на рефакторинг и улучшения
И так, ситуация X времени назад. У нас есть несколько фичевых команд, которые разрабатывают новую функциональность. Команды самостоятельно реализуют фичу от начала до конца и могут менять любой необходимый для этого код. У каждой команды свой продакт-менеджер, он управляет фокусом и бэклогом команды. Цель продакт-менеджера — достигать определённых бизнес-метрик.
Между продакт-менеджером и инженерами часто возникает типичный конфликт «качественно» vs «быстро».
Продакт топит за скорость, инженеры говорят что «тут надо бы порефакторить». С одной стороны, команде нужно проверить гипотезу и вывести функциональность здесь и сейчас. С другой стороны, нужно адаптировать или упростить архитектуру и избавиться от костылей, чтобы в будущем не иметь проблем.
Конфликт интересов возникает не потому, что продакт-менеджеры не понимают ценности качественной архитектуры, а команда не хочет создавать новую функциональность. Проблема в том, что некоторые изменения в архитектуре требуют фокуса всей команды на несколько месяцев, а для этого нужно отказаться от вывода новых фичей. В быстрорастущем продукте сделать это крайне сложно.
Чтобы не проседать в качестве, договорились выделять 20% времени на рефакторинг. Работает плохо, если внедрять поверх большой кодобазы. Помогает немного тюнить код, в контексте которого ты сейчас находишься, но не более.
Проблема подхода в том, что он не позволяет делать системных улучшений, над которыми надо сфокусировано работать несколько недель или месяцев. Подход требует хорошо поставленных процессов в команде, чтобы даже эти 20% не отдавались под фичи.
Но самое главное — это мотивация разработчиков делать качественно, не идти на уступки и не писать временные костыли.
Желание сделать правильно и надолго хорошо работает, пока команда небольшая и все понимают, что притронутся к этому коду ещё не раз. Но когда команда растёт и разработчики начинают специализироваться на конкретных областях, гораздо легче согласиться на компромисс и воткнуть костыль в коде, к которому ты больше не вернешься.
Закрепим ответственность за кодом за конкретными командами
Суть подхода — вся кодовая база делится на логические компоненты, которые закрепляются за конкретными командами. Команды реализуют новые фичи самостоятельно от начала до конца. И могут при наличии компетенций и желания изменить любой необходимый для этого код. Но если ты меняешь код не своего компонента, ты должен получить апрув от его владельца.
Что значит «владеть кодом»:
Ревьюить пул-реквесты на любые изменения;
Согласовывать технические решения от других команд по большим изменениям;
Помогать и консультировать другие команды;
Приводить код в соответствие критериям качества, принятым в компании, например, иметь тесты и документацию на код;
И, разумеется, исправлять ошибки на проде.
Как это выглядит с технической точки зрения.
В репозитории лежит файлик, в котором замаплены конкретные файлы и папки на конкретные команды. При создании пул-реквеста, если код в файле меняется, все релевантные участники команды автоматически добавляются как ревьюеры. Т.е. мимо команды-владельца не пройдут изменения в коде, за который она отвечает.
Самая большая проблема при внедрении этой практики с уже большой кодовой базой — договориться, кто за что отвечает. Без поддержки от менеджмента инициатива не взлетит.
По ходу распределения компонентов у нас «вскрылось» довольно много кода, который в принципе не ложился на фичевые команды в долгосрочной перспективе.
Что делать? Создадим новый тип команд — core-команды, которые будут отвечать за развитие «фундамента».
Вынесем фокус на архитектуру и системные задачи в отдельные core-команды
Это позволит не смешивать фичевые и системные беклоги в одной команде. Таким образом, у core-команд будет постоянный фокус на развитии архитектуры продукта, а беклогом этих команд будет управлять техлид или архитектор.
Цель core-команд — развитие внутренней платформы, на базе которой остальные команды смогут быстрее и качественнее строить новую функциональность и развивать существующую.
Примеры задач core-команды:
Упрощение доменной модели данных и предоставление API для неё;
Создание внутреннего фреймворка для фичевых команд;
Изоляция слоёв приложения;
Стабильность и производительность сервиса;
Проведение серьёзных архитектурных рефакторингов, которые разблокируют реализацию новой функциональности, недоступной нам сейчас.
Хочу отметить отличие core-команд от инфраструктурных. Инфраструктурные команды отвечают за окружение, выводы релизов и отказоустойчивость горизонтальных сервисов, которые мы используем, например, баз данных. Т.е. инфраструктурные команды почти не сталкиваются с продуктовыми задачами.
Core-команды, работают с бизнесовым кодом и без тесной работы с продакт-менеджментом не могут строить долгосрочных планов.
Окей. Создали команды. Будет ли этого достаточно или что-то может пойти не так?
Разумеется может.
Например, бизнес может продавить и превратить core-команду в ещё одну фабрику по созданию фичей, просто потому что на этапе роста они всегда в приоритет. Или команда может погрязнуть в разгребании чужих ошибок и костылей. Или может рефакторить систему, которую больше не планируют развивать. Или может потерять связь с реальностью и разрабатывать сферическую архитектуру в вакууме, которая качественно ничего не изменит.
Есть много способов уйти не туда. Вопрос — как сфокусироваться на важном?
Как понять, за что браться в первую очередь? Когда продукт большой, сложностей много и все они в разных местах, много запросов от фичевых команд на доработку ядра. Важно сфокусироваться не на локальных оптимизациях, а на стратегически важных вещах, которые помогут при игре в долгую. Для этого нужна долгосрочная техническая стратегия, выработанная вместе с бизнесом, которая определит развитие продукта на два года и более.
Создадим техническую стратегию
Стратегия должна помочь core-командам понять, что на самом деле мы собираемся построить не через месяц, а через несколько лет. Не так важно, как это называть: техническая стратегия, архитектурное видение, Painted Picture, просто план, — важнее это формализовать, чтобы перенести идеи из головы в документ и договориться, что все согласны со стратегией и понимают её одинаково.
Техническая стратегия включает три источника требований:
Бизнес: видение развития компании, продуктовая стратегия, крупные фичи, которые мы планируем реализовать в будущем, но не можем из-за блокеров в архитектуре;
Надёжность и производительность: какой ожидается рост нагрузки, под что стоит оптимизировать производительность, какие метрики для нас приоритетные;
Бэклог проблем, которые нас замедляют или мешают жить здесь и сейчас. Удобство разработки. Соответствие качества кода стандартным практикам из индустрии.
Первый черновик стратегии мы получили, запершись на несколько дней в переговорке с наиболее опытными разработчиками, и после нескольких мозговых штурмов создали договорились о следующем:
Целевое разбитие на компоненты и слои из которых должно состоять приложения.
Зоны ответственности команд: явно договорились, какие команды за какие слои и компоненты отвечают;
Целевая модель данных: объекты в системе и взаимосвязи между ними;
Перечень крупных изменений в core-компонентах, которые ускорят или упростят разработку, а также разлочат продуктовые фичи.
Следующим шагом мы завалидировали стратегию с техническим и продуктовым менеджментом. Собрали обратную связь, внесли корректировки.
Рассказали стратегию всем командам, чтобы при проектировании новой функциональности они учитывали целевую архитектуру и старались вырабатывать решения которые нас будут приближать к ней, а не отдалять.
Наличие документа, а не устных договоренностей — важный шаг. Документ сделал видение явным, на него можно теперь ссылаться при проектировании и понимать, насколько эта конкретная задача ложится на нашу архитектуру.
А дальше началась регулярная работа по воплощению видения в жизнь. Планирование, реализация, внесение корректировок. И регулярная коммуникация про изменения всем заинтересованным участникам.
Итого
Шаги, которые могут помочь сохранить нужный уровень гибкости и скорости разработки новой функциональности, при условии быстрого роста продукта и размеров команды:
Дать фичевым командам выделенное время на работу с техническим долгом;
Договориться о политиках владения кодом и превратить это в процесс;
Определить долгосрочную продуктовую стратегию и требования от бизнеса: понимаем, насколько текущая архитектура удовлетворяет этим требованиям. Если не удовлетворяет и инвестиций надо много, т.е. изменения не реализовать силами текущих команд в проектном режиме, то создаём отдельные команды из ребят с хорошим знанием продукта. И эти команды фокусируем на приведение архитектуры к целевой;
Целевую архитектуру вырабатываем вместе с бизнесом, фиксируем описание, планируем реализацию;
Далее фигачим, регулярно актуализируем видение, не забывая рассказывать о ключевых изменениях всем командам.
По некоторым практикам мы еще в самом начале пути. Например core-команды существуют всего полгода, но благодаря формализированной стратегии уже стало на порядок проще общаться с продакт-менеджерами. А именно договариваться, какие фичи мы можем сделать сейчас, а какие нужно отложить пока не адаптируем архитектуру необходимым образом.
В конце добавлю, что чем моложе проект и меньше размер команды, тем легче вводятся подобные практики. И что универсальных практик нет, и можно пробовать разные приемы направленные на качество, но лучше всего работает старое доброе желание сделать хорошо у всех участников команды.