hh и в продакшн: как выпустить новую фичу
Однажды техдепу в HeadHunter сделали толстовки с принтом на спине «hh и в продакшн». Вроде бы и забавно, но меня всё время смущала эта надпись, т.к. в оригинале это не то, чем стоит гордиться. Это натолкнуло меня написать пост, что же на самом деле означают буквы hh.
Я хочу рассказать о том, как рождается фича в HeadHunter на примере команды API, в которой я тружусь. Какой путь ей предстоит пройти от идеи до выхода в продакшн. Затрону как технические, так и менеджерские аспекты.
Мы так легко и беззаботно рождаем детей, но так мало мы заботимся о создании человека! Мы все тоскуем о каком-то прекрасном человеке. В нашей воле помочь ему явиться на земле! Так израсходуем же нашу волю, чтобы он явился скорее, и, может быть, мы будем вознаграждены за это счастье видеть среди нас юных предтеч того, о ком так давно тоскует наша душа.
Максим Горький
Сразу хочу определить, что под фичей подразумевается некая новая бизнес-единица приложения, что-то, что может пощупать конечный пользователь.
У нас в департаменте разработки 17 команд, все они самоорганизующиеся — это раз, а два: у каждой команды есть своя специфика, особенности, присущие их роду деятельности. Поэтому в других командах рабочие процессы могут отличаться. Я буду рассказывать, как это видит разработчик команды API.
Этап первый: идея
Итак, нам нужна новая фича. Чтобы она была, для начала её стоит придумать. Придумывать могут разные люди, но чаще всего это делают те, кто испытывает в ней необходимость. Будем называть таких людей заказчиками. Один из самых крупных заказчиков у команды API — департамент разработки мобильных приложений, которому постоянно что-то нужно для соискательских и работодательских приложений.
Чтобы заказчики и разработчики нашли общий язык, нужен определённый интерфейс. У нас он представлен в виде менеджера проектов (МП). Когда у заказчика возникает потребность в новой фиче, он приходит к МП. Они вместе выясняют бизнес-требования. После того как стало ясно, что нужно заказчику, МП заводит в Jira задачу, в которую вносит все основные требования. Такие задачи у нас называются портфелями, у них у всех один тип в Jira и общий набор статусов, используемых для продвижения новой фичи от идеи до её реализации.
Сбор и выявление требований — очень важный этап. Заказчик и менеджер должны хорошо понимать друг друга, и не менее важно, чтобы оба хорошо ориентировались в области, о которой пойдёт речь. Иногда имеет смысл ещё на этапе формирования идеи подключить к вопросу разработчиков в качестве консультантов, чтобы избежать технических проблем в будущем.
Этап второй: планирование
У нас есть записанная от заказчика идея. На этом этапе требуется чётко сформулировать задачу для разработчиков. Выяснить, насколько сложно реализовать задачу в целом, определить ключевые подходы к решению задачи. Найти подводные камни. Иногда реализовать идею может быть очень затратно, поэтому приходится находить определённый баланс между сложностью и требованиями. На этом этапе может быть задействовано большое количество людей, пока все договорятся. Если задача требует сложного или неоднозначного архитектурного решения, то оно нередко выносится на AB (Architecture Board). В hh.ru это еженедельное собрание тимлидов с техническим директором для решения и обсуждения насущных проблем и шаринга знаний между командами.
Иногда становится понятно, что задача слишком большая или содержит в себе несколько задач. Тогда она может быть поделена на отдельные портфели.
Часто при реализации новой идеи имеет смысл вначале сделать MVP (minimum viable product) — минимально жизнеспособный продукт, который будет иметь только минимальное количество функций, достаточное для жизни продукта. Так можно проверить, как этот продукт зайдёт и стоит ли его развивать вообще, или сделать рабочую версию в короткий срок, если полную версию делать слишком долго.
Этап третий: декомпозиция и оценка
Декомпозиция
Когда становится понятно, что представляет из себя задача в техническом плане, её можно декомпозировать — разбить на этапы и подзадачи. Чаще всего декомпозицией занимается один из разработчиков. Он проводит более детальное исследование портфеля с технической стороны. Выясняет непонятные моменты у мейнтейнеров кода, с которым придётся работать. На каждую подзадачу заводится таска в джире. Каждая таска представляет собой отдельную задачу, изменение, которое можно выкатить на прод — и ничего не сломается. Какие бывают таски? У нас в команде API, если расширяется интерфейс, — а он в идеале, кстати, может только расширяться, чтобы не нарушить обратную совместимость, — то заводится задача на доработку документации.
Представим, что мы выдаём какие-то новые данные в API. Тогда нам может ещё потребоваться задача на выдачу этих данных в приложении API, а чтобы эти данные протащить из бекэнда, надо создать задачу на доработку интеграционного слоя, а ещё надо сделать правильную выдачу данных в самом бекэнде — для этого тоже нужна отдельная задача. Изменение функциональности должно сопровождаться написанием автотестов, которые прогоняются перед выходом задач в продакшен, чтобы повысить уверенность в том, что плохой код не попадёт в работу. Для автотестов, естественно, тоже нужна задача. Все эти задачи блокируются друг на друге в джире в нужной последовательности, чтобы они могли выйти в правильном порядке. Также задачи привязываются к соответствующему портфелю.
Декомпозиция — это трудоёмкий процесс. Именно он определяет этапы работы над новой фичей. Он требует знания архитектуры дорабатываемой системы и понимания протекающих процессов. На этом этапе часто приходится анализировать существующий код, чтобы понять, куда встроить новый.
Оценка
Когда декомпозиция завершена, вся команда собирается вместе поиграть в покер. В planning-покер. Смысл его заключается в том, чтобы дать оценку сложности выполнения задачи. Теперь я задачами называю не отдельную фичу, которую делаем, а её подзадачи, которые можно выпускать отдельно. Берётся каждая задача из портфеля, немного обсуждается со всеми разработчиками, чтобы всем было понятно, что в ней примерно придётся делать, а дальше каждый даёт оценку сложности этой задачи, выбирая специальную карту с нужным значением. Потом все в один момент открывают карты — и в идеале у всех должна получиться одинаковая оценка. Если кто-то выбивается из мнения большинства, он должен аргументировать свою позицию. После выслушивания аргументов и контраргументов можно повторить ещё один заход или прийти к общему мнению. В итоге в задачу записывается её сложность. Это нужно для того, чтобы прогнозировать время выполнения портфеля в целом.
Раньше мы давали оценки задач в story point-ах (sp). 1sp — это один идеальный рабочий человеко-день, т. е. примерное представление, за сколько ты сделаешь эту задачу, если сядешь, будешь заниматься только ей и никто и ничто тебя не будет отвлекать. Но оценка в sp оказалась для нас не слишком удобной, т. к. она довольно точная, что требовало определённого времени на каждую задачу для всех и оценка иногда затягивалась. Тогда мы решили оценивать в майках.
Большая точность оценки портфеля там не требовалась. Более того, оценки, выставленные в сторипоинтах, тоже не всегда оказывались точными и немного плавали как в большую, так и в меньшую сторону. Так что же это за майки? Оценка в майках — это нередкая практика в agile. Обычно её используют как переходную к сторипоинтам. У нас же вышло наоборот. При оценке оперируют не самими майками, а размерами. Мы для себя определили такую градацию. На какое описание больше всего задача подходит, то и назначаем.
- 0 — ничего делать не надо;
- XS — ерунда, можно очень быстро сделать;
- S — простая задача;
- M — обычная задача, всё в ней понятно;
- L — сложная, есть непонятные места;
- XL — сложная, мало что понятно. Таких задач не должно быть. Либо надо отправить её на дополнительное исследование, либо она должна быть разбита на подзадачи;
- XXL — в jira добавили эту майку, но не знаю зачем — таких тоже быть не должно. Эта задача как целый портфель, наверное.
Есть много приложений для телефонов для игры в planning-покер с майками и сторипоинтами.
После того как все задачи оценены, можно просуммировать майки. Каждой майке сопоставлено какое-то число, отражающее сложность. Сумма этих чисел определяет примерную сложность задачи. Не так важно, чему равны эти числа, они не отражают реальное время выполнения портфеля, но по ним можно примерно понимать, насколько один портфель тяжелее другого. Ещё эта оценка используется для того, чтобы считать технический налог. Команды должны на него тратить 30% своего времени. Технический налог — это задачи, не принадлежащие ни одной команде и направленные, как правило, на улучшение чего-либо. Например, выпиливание старого кода, рефакторинг, переход на новые библиотеки, повышение стабильности/производительности, добавление новых удобств и т. д.
Этап четвёртый: разработка
Стендап
Прежде чем рассказывать о самой разработке, хочу рассказать о таком полезном процессе, как стендап (или планёрка, если хотите), сопровождающем разработку. Каждый рабочий день команда собирается в переговорке. У нас есть список тем, которые мы ежедневно обсуждаем и 30 минут времени на это обсуждение. Впереди всегда самая насущная тема: проходимся по портфелям, начиная с самых готовых, и выясняем, что ещё требуется, чтобы выпустить этот портфель, и стараемся все силы пустить на него. Также на стендапах мы обсуждаем проблемы, с которыми сталкиваемся в работе, делимся важной информацией. Вообще, про коммуникации в hh.ru можно рассказывать много, это тема отдельного поста.
Одно лишь хочу ещё сказать: не бойтесь задавать вопросы. Даже если они кажутся вам глупыми и элементарными. Мне, например, иногда попадаются вопросы, ответа на которые я не знаю, в то же время я думаю, что его должен знать каждый, и мне стыдно спрашивать. Но уж куда лучше спросить и узнать, чем продолжать быть невежественным. К тому же другие на самом деле редко думают, что им задают глупые вопросы, а скорее наоборот человеку иногда даже приятно показать свою компетентность. Бывают, конечно, исключения со всякими назойливыми, не очень вменяемыми или сообразительными людьми, но я надеюсь, что читатель не относится к этой категории.
Выбор задачи
Мы постепенно постигаем философию Kanban, одно из правил которого гласит, что необходимо уменьшать количество портфелей, находящихся одновременно в работе. Задачи иногда блочатся друг на друге и следующую нельзя взять, пока не будет готова предыдущая. Или, бывает, портфель приостанавливается, т. к. ожидает решения каких-то других проблем. Из-за этого у нас обычно одновременно в работе 2–4 портфеля. В нашей команде сейчас 4 разработчика и мы ищем пятого (миг-миг). В команде приветствуется кросс-функциональность. Любой из нас может выбрать из портфелей задачу на Java, Python или взять задачу на автотесты или документацию. Можно брать любую задачу из портфелей, которая нравится, но есть и некоторые нестрогие правила. Например, если портфель по расширению интерфейса API, то первой обычно выполняется задача по документации, т. к. на этапе документирования проявляется более чёткая картина того, как интерфейс будет выглядеть, и в деталях будут учтены все правила. Имея документацию легче разрабатывать. Очерёдность задач диктуется приоритетом портфелей, ну и здравым смыслом — проще взять задачу во фронтэнде, когда на бекэнде для неё будут готовы данные, но и такие задачи часто можно делать параллельно.
Разработка
Мы выбрали задачу. Наконец-то можно начать разрабатывать! Открываем свою любимую IDE (слава мавену) и начинаем писать код. Для тестирования есть большой простор. У каждого разработчика есть свой тестовый стенд. На стенде развёрнуто окружение, очень близкое к продакшн-среде. Самый простой способ проверить сервис — запустить его локально. Настройки сервиса позволяют направлять запросы на тестовый стенд для взаимодействия с остальными сервисами. Иногда этого бывает недостаточно и нужно запустить сервис в контексте других сервисов, например, подправить бэкэнд и подёргать нужные страницы сайта. Для этого можно задеплоить сервис из ветки git-а на стенд. Код принято покрывать юнит-тестами. Они прогоняются перед деплоем на стенд, и, если что-то пошло не так, мы об этом узнаем.
Про разработку не буду ничего говорить. И так всего навалом. Просто делайте своё дело хорошо, чтобы совесть ваша была чиста, но не слишком увлекайтесь, чтобы успевать в срок.
Ревью
Обязательная часть после внесения правок в код — это ревью. Ревью помогает шарить знания между разработчиками и улучшать качество кода. Как я выбираю ревьювера. Если правил что-то сложное, то лучше отнести код человеку, который лучше всего в этом разбирается. Задачи из сервиса API обычно ревьювятся членами API — хорошо знать, кто что делает в команде, плюс мне нравится, что у нас ревью проходит оперативно. У сервисов есть мейнтейнеры и другие неравнодушные, подписанные на проект на гитхабе, некоторые из них любят посматривать pull-реквесты, даже если их не просят, за что им спасибо. Вообще, ревьювить стоит как можно скорее, чтобы задача не зависала по этой причине. Ревью обычно не требует много времени, а задачи надо проталкивать вперёд.
Автотестирование
Ещё одной обязательной частью выпуска задачи является автотестирование. Это нужно для того, чтобы проверить работоспособность основных мест после внесённых правок. Это не серебряная пуля, но у нас хорошее покрытие тестами и от большого количества проблем это спасает. Для того чтобы прогнать автотесты, надо задеплоить изменённый сервис на стенд и запустить джобу в jenkins. Запустятся TestNg-тесты, которые будут ходить по основным интерфейсам сайта, API и других мест и проверять корректность их работы.
Релиз
После того как автотесты прошли, задачу можно перевести в джире в статус Ready to release. Все задачи из одного сервиса с таким статусом и не заблокированные другими задачами можно выпустить за один раз. Есть сервисы, по которым выпускается много задач. Такие релизятся каждый день. Остальные выпускаются по мере необходимости. Я пришёл в HeadHunter два года назад, тогда надо было произвести достаточно много действий, чтобы выпустить релиз. Была длинная инструкция. До того, как я пришёл, говорят, было ещё сложнее. За ежедневные релизы отвечали по очереди разные команды. В команде обычно выбирался один ответственный. В его задачи входило выполнять действия из инструкции, заниматься проблемами, если они возникали в процессе релиза, и следить за тем, чтобы всё было хорошо. На сегодняшний день мы очень сильно продвинулись в выпуске релизов, которые теперь выходят в автоматическом режиме и вмешательства человека требуется не много, но тем не менее команды продолжают дежурить с релизами. Если коротко, то при релизе происходит следующее:
необходимо обновить релизный стенд до актуального состояния’;
собрать в git-ветку release-candidate, где будут смержены все выпускающиеся задачи;
задеплоить сервис из release-candidate на тестовый стенд;
запустить автотесты;
задеплоить release-candidate на прод. Этот этап происходит отдельно средствами службы эксплуатации;
вмержить release-candidate в master.
Автомат работает хорошо. Сейчас вмешиваться нужно, только если что-то пошло не так. Например, может быть такое, что релизный стенд не обновился по какой-то причине или при сборке релиз-кандидата произошёл конфликт или, например, не прошли автотесты. Если задачу выпустили и что-то всё же пошло не так, всегда можно быстро откатить до предыдущей рабочей версии.
A/B-тестирование
Некоторые команды перед тем, как выпустить фичу в жизнь, проводят A/B-тестирование. Фича выпускается на отдельную группу пользователей, работает так какое-то время, собираются данные, а в конце сравнивается, кто показал лучшие результаты — новая фича или то, что было раньше. Если новая фича победила, то у неё есть право на жизнь. Всё, конечно, немного сложнее.
Slack
Отдельно хочу отметить участие слака в процессе разработки и коммуникаций вообще. Там, конечно, полно флуда, иногда он отвлекает и приходиться учить себя правильно им пользоваться, чтобы он приносил больше пользы. Во-первых, там всегда можно компетентным людям задать вопрос, особенно если работаешь удалённо, во-вторых, это удобное место для шаринга знаний, документации и пр. В-третьих, подключение к нему всевозможных штук: например, получение нотификаций, что стенд обновился, события из календаря, алерты от системы мониторинга и многое другое.
Этап пятый: демонстрация и фидбэк
Наконец, когда все задачи портфеля выпущены, задача, поставленная перед портфелем, выполнена, можно похвастаться тем, что получилось. Приглашаются заказчик, другие заинтересованные лица и опционально все желающие и проводится небольшая презентация по проделанной работе и даются ответы на вопросы. После портфель в джире переводится на заказчика, чтобы тот оставил фидбэк о проделанной работе. На этом жизненный цикл портфеля заканчивается.
Кстати, демонстрация иногда проводится и до выпуска в продакшен, чтобы собрать отзывы, мнения, вопросы и пожелания, поделиться, похвастаться и просто понять, что движение идёт в верном направлении.
А ещё?
А ещё я хочу сказать, хотя наблюдательный читатель уже заметил, что жизненный цикл портфелей и задач управляется в Jira. У них есть разные статусы, на которые завязаны всяческие процессы. Ещё эти статусы полезны тем, что можно, например, смотреть за статистикой/динамикой отдельной команды или всех команд в целом, делать выводы, в каких местах затыки, и, может, там возможно что-то улучшить. Для отображения портфелей и задач мы используем kanban-доски.
Вот так труден и тернист путь фичи к жизни. На этом пути поджидает её множество испытаний, но это полностью оправданный путь. В итоге мы получаем сильную и сформировавшуюся фичу, способную выдержать все испытания.
В заключение хочу сказать, что не надо устраивать из процесса карго-культ. Надо осознавать, что приносит пользу, а что нет. И не стоит менять всё сразу. Но надо пробовать, экспериментировать, выбирать лучшее, постепенно меняться.
Автор: