Подумайте о мире, в котором вам не нужна отдельная среда тестирования, где вы можете тестировать все в действии и собирать ценные данные, которые помогут вам совершенствоваться на этом пути. Секретный ингредиент: функциональные флаги.

Если вы не можете решить, является ли тестирование в продакшене глупой или гениальной идеей, этот туториал определенно поможет.

Что такое флаги функций?

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

Флаги функций определяют, какие пути кода активны в данный момент времени. Эти флаги, также известные как переключатели функций, переключатели или флипперы, можно включать и выключать — либо во время сборки, либо во время выполнения, что позволяет команды для изменения поведения приложения без необходимости обновления кода.

Преимущества флагов функций

Несмотря на добавление уровня сложности в кодовую базу, функциональные флаги эффективны, когда речь идет о доставке программного обеспечения:

  • Короткий цикл разработки: без флагов функции мы должны отложить развертывание функции до ее тщательного тестирования — процесс, который может занять недели. С ними мы можем развертывать несколько раз в день, пробовать частично разработанные функции и мгновенно получать отзывы.
  • Упрощенный контроль версий: мы можем избавиться от долгоживущих тематических веток. Флаги функций поощряют использование магистральной разработки. Мы можем объединяться каждый день, непрерывно интегрироваться, сводить к минимуму конфликты слияний и выполнять итерации гораздо быстрее.
  • Тестирование в рабочей среде: новые функции могут быть первоначально включены только для разработчиков и пользователей бета-версии. Нет необходимости в отдельной тестовой среде.
  • Разделите деловые и технические решения: иногда функция готова, но мы совсем не готовы ее опубликовать. Флаги функций позволяют нам включать их, когда это наиболее целесообразно для бизнеса.
  • Детальные выпуски: флаги функций обеспечивают высокий уровень контроля для проведения канареечных или сине-зеленых выпусков.

Примеры использования флагов функций

Прежде всего, флаги функций используются для выпуска новых функций. В дополнение к канареечным запускам вы можете делать интересные вещи, такие как активация функций на сезон (например, Черная пятница) и установка переключателей для каждого пользователя или региона. Никакая другая техника не предлагает такой степени контроля.

Дорожная карта для использования флагов функций:

  1. Код: разверните новую функцию, которая изначально отключена для всех.
  2. Тест: когда функция готова, включите ее для внутренних тестировщиков и разработчиков.
  3. Canary/Beta: после достаточного тестирования и достаточного количества итераций включите его для пользователей бета-версии или процента от общего населения.
  4. Итерация: сбор показателей и аналитика использования. Соберите отзывы. Продолжайте повторять.
  5. Выпуск: наконец, включите эту функцию для всех.

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

Запуск экспериментов с флагами функций

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

Допустим, вы хотите добавить призыв к действию, и у вас есть два варианта. Один из них представляет собой короткую форму и кнопку. Другой, одна большая круглая кнопка. Какой из них получает больше кликов?

Итак, вы показываете половине пользователей одну версию, половине другую. Собрав данные об использовании за период, вы сможете ответить, какой из них лучше. Один последний переключатель активирует альтернативу-победитель для всех.

Переключатели функций в качестве операционных переключателей

Не все переключатели являются временными. Некоторые из них могут быть постоянными. Наличие ограниченной или «облегченной» версии может спасти жизнь в периоды повышенного спроса.

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

Флаги как альтернатива ветвлению

У вас есть отличная идея для новой функции. Каков ваш первый инстинкт как разработчика? Чтобы быстро получить последнюю версию, создайте новую ветку и приступайте к работе. Примерно через 30 минут у вас есть рабочий прототип. Все тесты проходят, и все выглядит хорошо.

Тем не менее, вы не решаетесь интегрировать ветку в основной ствол, потому что функция неполная. Есть еще работа. Таким образом, вы держите ветку изолированной в течение нескольких дней. Что может пойти не так, верно?

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

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

"Если вы выполняете слияние каждый день, вы никогда не дойдете до того момента, когда у вас возникнут огромные конфликты слияния, которые трудно разрешить". — Линус Торвальдс

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

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

Реализация флагов функций

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

Мы думаем, что сможем улучшить движок, используя машинное обучение. Гипотеза состоит в том, что получение лучших предложений приведет к увеличению продаж. Но создание правильной модели требует времени, поэтому мы не готовы выпустить ее за одну ночь. Мы хотим иметь пространство для экспериментов.

Итак, разворачиваем обе версии движка:

// returns the active recommendation engine
function engineFactory(){
    let useML = false;
    //let useML = true;  // UNCOMMENT TO ENABLE NEW ENGINE

    if(!useML){ // SINGLE TOGGLE POINT
        return classicRecomendationEngine();
    }
    else{
        return MLRecomendationEngine();
    }
}

let recommended_products = engineFactory()(viewed_product)

Этот шаблон позволяет нам иметь точку переключения в одном месте. Очень важно отделить точку принятия решения от остального кода; в противном случае мы будем if-then-else появляться повсюду.

Комментарии могут работать для быстрых экспериментов, но это громоздко. Можем ли мы сделать лучше? Вместо него у нас может быть маршрутизатор, который знает состояние каждого флага функции и возвращает правильный механизм. Пока интерфейс к движку стандартизирован, они взаимозаменяемы. Этот шаблон называется ветвь по абстракции.

// toggle router for engine
function engineFactory(){
    if(features.isFeatureEnabled("use-machine-learning-engine")) { // TOGGLE ROUTER
        return MLRecomendationEngine();
    }
    else{
        return classicRecomendationEngine();
    }
}

// instantiate the correct engine and use it
let recommended_products = engineFactory()(request.viewed_product);

Флаги функций не ограничены значениями true или false. У нас могут быть многозначные флаги. Например, мы можем написать низкокачественный наивный движок, который использует меньше ресурсов для обработки всплесков трафика.

Чем больше у нас путей кода, тем больше тестов нам нужно написать. Модульный тест для механизма рекомендаций должен охватывать все поддерживаемые альтернативы.

describe("Test recommendation engines", function() {
    it("works with classic engine", engine => {
        const recommended_products = engineFactory('classic')(current_product);
        // check results
    });

    it("works with ML engine", engine => {
        const recommended_products = engineFactory('machine-learning')(current_product);
        // check results
    });

    it("works with ML engine", engine => {
        const recommended_products = engineFactory('naive')(current_product);
        // check results
    });
});

Включение флагов функций

Теперь, когда мы закодировали наши первые функциональные флаги, пришло время решить, как их включить. Вопрос сводится к двум вариантам: при запуске или во время выполнения.

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

Переключатели среды выполнения

Статические флаги недостаточно гибки для вас? Вам нужно повысить сложность: вам нужна база данных флагов функций. Это позволяет изменять настройки «на лету», проверять изменения и регистрировать использование функций. Но не забывайте, что вам также придется изящно управлять изменениями состояния флага.

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

Флаги функций и CI/CD

Давайте посмотрим на флаги функций с точки зрения CI/CD. Когда вы практикуете непрерывную интеграцию, вы должны решить, как вы будете тестировать флаги функций. Есть два способа сделать это:

Во флагах стадии сборки состояние всех флагов известно во время сборки. Поскольку вы определяете каждый флаг в начале конвейера CI, вы тестируете ту же сборку, которая в конечном итоге развертывается.

Обратная сторона заключается в том, что нам нужно перестроить и повторно развернуть приложение, чтобы изменение флага функции вступило в силу.

📙 Такие стеки, как Docker и Kubernetes, позволяют развертывать обновления приложений без перерыва. Обязательно ознакомьтесь с нашей бесплатной электронной книгой: CI/CD для Docker и Kubernetes.

Флаги времени выполнения и CI/CD

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

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

Рекомендации по использованию флагов функций

  • Используйте их с мерой. Флаги функций могут выйти из-под контроля.
  • Никогда, никогда, никогда не переделывайте флаг. Всегда создавайте новый для каждого изменения, иначе рискуете совершить еще одну ошибку на полмиллиарда долларов.
  • Минимизируйте долг флага. Удалите старые и неиспользуемые флаги. Не позволяйте мертвому коду оставаться долго.
  • Если у вас нет веских причин не делать этого, ограничьте срок действия флага функции (недели).
  • Выберите описательные имена для ваших флагов. New-Feature-2 НЕ хорошее имя.
  • Внедрите магистральную разработку и непрерывную доставку.
  • Рассмотрите возможность просмотра состояния всех ваших флагов. Вы можете написать панель администратора или API для этого.
  • Отслеживайте и проверяйте использование флагов.

Последние мысли

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

Далее читается:

Первоначально опубликовано на https://semaphoreci.com 4 января 2022 г.