Проблемы доставки фич в больших проектах

    Любому продукту, который в данный момент находится в сторе, грозят релизы. Наш проект не является исключением. Мы работаем по методологии scrum, разработка делится на спринты, обычно спринты не привязаны ко времени, а делятся на временные отрезки, зависящие от скоупа спринта. По итогам спринта обычно проводится релиз приложения в стор, который включает в себя новые фичи и некоторый багфикс.


    Однако, из-за специфики проекта не все фичи сразу попадают в релиз по завершению спринта. Из-за такого подхода появляются фичи, которые нельзя выпускать в прод. При всем при этом, никто не отменял критические баги, мелкие фиксы и просто выпуск готовых фич. Можно выделить несколько видов релизов:


    • Итерационная разработка. Выпуск готовых и согласованных фич.
    • Разное время приемки фич. Каждая фича имеет разный приоритет со стороны заказчика, что определенно влияет на сроки релиза даже уже готовых фич.
    • Критические баги и SLA. При обнаружении реальных проблем на продовых сборках необходимо в кратчайшие сроки исправить баг и выкатить обновление.

    В условиях постоянных релизов возникает вопрос: «Как же вести разработку?».
    Ведь каждый разработчик должен делать задачи, делать ветки на эти задачи и куда-то по итогу их мерджить.


    image


    Изначально в студии был организован подход «фича-веток», давайте рассмотрим его концепт, выделим плюсы и минусы.


    image


    Идея этого подхода максимально простая. Каждый раз, когда разработчик начинает работу над новой фичей, создает для нее отдельную FeatureBranch, что позволяет абстрагироваться от текущей DevBranch. Таким образом, разработчик уходит со своей фичей в отдельный мир, не обращает внимания на то, что происходит в основной ветке, пилит фичу в отдельной ветке и все подзадачи направляет в FeatureBranch своей фичи.


    image


    После успешного завершения всей фичи разработчик благополучно пытается смерджить свой готовый код в основную ветку разработки, чтобы впоследствии сделать релиз. Но не тут то было, со 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
    }

    А для того, чтобы любой желающий мог получить доступ к скрытой функциональности, мы реализовали простой экран, на котором можно «на горячую» ее включить.


    image


    Выводы


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


    Из минусов:


    • Не все изменения можно скрыть за FeatureToggle.
    • Надо понимать, что хоть и скрытая, функциональность может вносить импакт в некоторые части приложения.

    Плюсы:


    • Нет больше необходимости поддерживать большие фича ветки.
    • Уменьшилось количество конфликтов.
    • Релизы ускорились.
    • Появилась возможность быстро скрывать и открывать нужную функциональность.
    • Больше нет необходимости пересобирать проект, чтобы протестировать давно забытую функциональность приложения.

    Что в итоге?


    Цель этого цикла статей не просто поделиться тем или иным подходом с большой аудиторией, я хотел приоткрыть занавес над разработкой мобильных приложений. Достаточно много статей про разные подходы в отдельности, но редко кто рассказывает про темную сторону разработки, которая скрыта от глаз. За полтора года разработки банковского приложения мы поняли — нельзя сразу реализовать идеальный проект. Разработка большого банковского приложения – это вызов, с которым справляются не все. Каждый спринт мы сталкивались с новыми проблемами и решали их теми способами, которые казались нам наиболее эффективными. Не все решения заходили, поэтому я постарался собрать только полезные выводы, которые сработали для нас.


    Вот список тем, которые мы обсудили в этом цикле статей:



    Спасибо за внимание!


    P.S.: Отдельное спасибо всей команде мобильного приложения Банка Зенит. Все решения и подходы были выведены только благодаря мотивации каждого члена команды сделать продукт лучше, а жизнь других чуточку проще.

    Surf
    Компания

    Комментарии 3

      0
      есть такая штука — git flow
      Если у вас scrum/agile и прочее, то как у вас таск/юзер стори может быть длиннее спринта (так, что нужно долго держать бранч)?
      ну и если нельзя добавить фичу, не внеся опасных изменений в остальные куски кода — это не очень хороший код.
        0
        Если у вас scrum/agile и прочее, то как у вас таск/юзер стори может быть длиннее спринта (так, что нужно долго держать бранч)?

        Релиз фичи зависит от множества факторов:


        1. Готовность бекенда, который на стороне банка
        2. Готовность инфраструктуры банка к релизу этой фичи
        3. Апрув от сторонних проверяющих организаций
        4. Смена повестки в банке.

        Разработка фичи заканчивается вместе со спринтом, но это не значит, что она может пойти в релиз.


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

        Любое изменение кода может нести в себе импакт.

          0
          Релиз фичи зависит от множества факторов:


          нет-нет, это понятно. Речь о том, что по эджайлу юзер стори/таск не может быть длиннее спринта; по окончании спринта все бранчи д.б. слиты в дев. Т.е., указанная проблема с долгими бранчами просто отсутствует
          Если у вас одна фича == одной юзер стори, и она длиннее спринта, это прямое нарушение принципов А.

          Любое изменение кода может нести в себе импакт.


          Ну так надо стремиться чтоб таких ситуаций было поменьше. Они ж все про одно и то же — сильно связанный код, изменение вместо добавления и так далее.
          А то будет, как один мой коллега в возрасте говорит — «Программирование — это тяжелый труд»
          Ну да, с его классами по 5 тыс строк и функциями по 600-1000 строк это безусловно тяжелый труд

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое