Любому продукту, который в данный момент находится в сторе, грозят релизы. Наш проект не является исключением. Мы работаем по методологии scrum, разработка делится на спринты, обычно спринты не привязаны ко времени, а делятся на временные отрезки, зависящие от скоупа спринта. По итогам спринта обычно проводится релиз приложения в стор, который включает в себя новые фичи и некоторый багфикс.
Однако, из-за специфики проекта не все фичи сразу попадают в релиз по завершению спринта. Из-за такого подхода появляются фичи, которые нельзя выпускать в прод. При всем при этом, никто не отменял критические баги, мелкие фиксы и просто выпуск готовых фич. Можно выделить несколько видов релизов:
- Итерационная разработка. Выпуск готовых и согласованных фич.
- Разное время приемки фич. Каждая фича имеет разный приоритет со стороны заказчика, что определенно влияет на сроки релиза даже уже готовых фич.
- Критические баги и SLA. При обнаружении реальных проблем на продовых сборках необходимо в кратчайшие сроки исправить баг и выкатить обновление.
В условиях постоянных релизов возникает вопрос: «Как же вести разработку?».
Ведь каждый разработчик должен делать задачи, делать ветки на эти задачи и куда-то по итогу их мерджить.
Изначально в Surf был организован подход «фича-веток», давайте рассмотрим его концепт, выделим плюсы и минусы.
Идея этого подхода максимально простая. Каждый раз, когда разработчик начинает работу над новой фичей, создает для нее отдельную FeatureBranch, что позволяет абстрагироваться от текущей DevBranch. Таким образом, разработчик уходит со своей фичей в отдельный мир, не обращает внимания на то, что происходит в основной ветке, пилит фичу в отдельной ветке и все подзадачи направляет в FeatureBranch своей фичи.
После успешного завершения всей фичи разработчик благополучно пытается смерджить свой готовый код в основную ветку разработки, чтобы впоследствии сделать релиз. Но не тут то было, со 100% вероятностью, в случае если он делал свою задачу достаточно долго, у него появятся конфликты. И очень хорошо, если эти конфликты будут только в каких-то swift файлах, но зачастую если фича томилась в этой ветке достаточно долго, проходя через все этапы согласования, тестирования и отладки, то конфликты начинают появляться и в xib файлах, и такие конфликты доставляют море удовольствия в момент их исправления. Сразу появляется импакт на этом экране, и время на мердж вырастает моментально.
Какие плюсы можно выделить в таком подходе?
- Фича абстрагирована от остального кода.
- Работа не блочится релизами.
- Нежелательный код не может утечь в прод.
Но в этом подходе есть и минусы:
- Сложность поддержки больших веток в случае откладывания релиза.
- Постоянные конфликты при работе.
- Большой импакт при мердже.
- Нет возможности быстро собрать сборку сразу с несколькими фичами.
Исходя из этих проблем, мы решили попробовать новый подход. Для нас он новый, а сам концепт известен давно, но использовался обычно в другом контексте.
FeatureToggle
Мы не придумали ничего нового в данном подходе, мы просто использовали его в нужное время и в нужном месте. Зачастую FeatureToggle используются в приложениях, чтобы проводить A/B тестирование или включать/отключать различную функциональность в зависимости от настроек на сервере. Мы стали использовать этот подход локально, чтобы решить свои проблемы при работе с ветками.
Суть такого подхода заключается в том, что больше не надо вести отдельные ветки для каждой фичи. Теперь на этапе проектирования и декомпозиции задачи решается то, каким образом мы ее будем вести. Если мы понимаем, что эту фичу можно легко скрыть за каким-то флагом, то больше никаких «фича-веток» мы не используем.
Работает это следующим образом:
public final class FeatureToggle {
public enum SomeFeature {
public static var isEnabled = true
public static var useMocks = false
}
}
У нас в приложение есть небольшой класс, который содержит статические изменяемые переменные. Каждая из них отвечает за доступность той или иной функциональности в приложении. Таким образом, каждый разработчик может вести разработку в текущей дев ветке, т.е. все его задачи мерджатся в текущую дев ветку, и при этом работа и релизы другой функциональности не блокируются тем фактом, что эта задача еще не завершена.
Применение этих флагов выглядит еще проще, чем можно подумать. Так как все это происходит локально, то нам нет необходимости получать какие-то настройки от сервера.
if FeatureToggle.SomeFeature.isEnabled {
// Should show feature
} else {
// Should hide feature
}
А для того, чтобы любой желающий мог получить доступ к скрытой функциональности, мы реализовали простой экран, на котором можно «на горячую» ее включить.
Выводы
Надо понимать такой подход – не панацея. Не каждое изменение можно скрыть за FeatureToggle. Например, при добавлении кеша в наше приложение мы понимали, что нельзя скрыть такой большой импакт, кеш задевал практически всю функциональность приложения. В этом случае мы использовали отдельную ветку для этой задачи, и конечно же, получили огромное количество конфликтов на выходе. Но зачастую практически любую новую функциональность, которую мы добавляем в приложение, мы можем скрыть за простыми флагами, которые упрощают разработку, тестирование и выход в релиз.
Из минусов:
- Не все изменения можно скрыть за FeatureToggle.
- Надо понимать, что хоть и скрытая, функциональность может вносить импакт в некоторые части приложения.
Плюсы:
- Нет больше необходимости поддерживать большие фича ветки.
- Уменьшилось количество конфликтов.
- Релизы ускорились.
- Появилась возможность быстро скрывать и открывать нужную функциональность.
- Больше нет необходимости пересобирать проект, чтобы протестировать давно забытую функциональность приложения.
Что в итоге?
Цель этого цикла статей не просто поделиться тем или иным подходом с большой аудиторией, я хотел приоткрыть занавес над разработкой мобильных приложений. Достаточно много статей про разные подходы в отдельности, но редко кто рассказывает про темную сторону разработки, которая скрыта от глаз. За полтора года разработки банковского приложения мы поняли — нельзя сразу реализовать идеальный проект. Разработка большого банковского приложения – это вызов, с которым справляются не все. Каждый спринт мы сталкивались с новыми проблемами и решали их теми способами, которые казались нам наиболее эффективными. Не все решения заходили, поэтому я постарался собрать только полезные выводы, которые сработали для нас.
Вот список тем, которые мы обсудили в этом цикле статей:
- Проблемы архитектуры в больших проектах
- Проблемы взаимодействия с внешними командами на больших проектах
- Проблемы инструментария в больших проектах
- Проблемы доставки фич в больших проектах
Спасибо за внимание!
P.S.: Отдельное спасибо всей команде мобильного приложения Банка Зенит. Все решения и подходы были выведены только благодаря мотивации каждого члена команды сделать продукт лучше, а жизнь других чуточку проще.
Кейсы, лучшие практики, новости и вакансии Surf — в телеграм-канале Surf iOS Team. Присоединяйтесь >>