Новое в журнале. Feature flags для интерфейса: как выпускать UI по частям
Практика и проекты

Feature flags для интерфейса: как выпускать UI по частям

Новый дизайн страницы редко безопасно выкатить всем сразу: один баг верстки в проде виден сразу ста процентам пользователей, а откат требует повторного деплоя. Feature flags для UI решают именно это — показывают новый интерфейс части аудитории, пока команда наблюдает за метриками, и откатывают его переключением значения, а не новым релизом кода.

13 ноября 20268 мин чтенияПрактикам
Feature flags для интерфейса: как выпускать UI по частям
5 шагов шкалыканареечный rollout 1→5→20→50→100%
4 типа флаговrelease, experiment, ops, permission
Коротко. Feature flag для UI — это переключатель, который решает, какую версию интерфейса видит конкретный пользователь, без отдельного деплоя под каждую версию. Это даёт поэтапный rollout (процент аудитории или канареечная шкала), мгновенный откат изменением значения флага и возможность держать в коде одновременно старую и новую версию компонента, пока идёт наблюдение за метриками.

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

Спроектировать такой rollout заранее — задача не только разработки: нужно заложить предсказуемые состояния компонента (старое и новое) и границы, по которым интерфейс можно безопасно включать по частям, ещё на этапе макета. Именно этим занимается команда, которая проектирует интерфейс и помогает поэтапно вводить его в прод — закладывая обе версии экрана и breakpoints для rollout в дизайн-систему, а не оставляя это на откуп разработчику в последний момент.

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

Что такое feature flags и почему это не только про бэкенд

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

Чем UI-флаг отличается от обычного feature-флага

Технически — почти ничем: то же условие, тот же источник значения. Разница в последствиях ошибки. Сломанная серверная логика часто остаётся незаметной для пользователя, пока не всплывёт в логах или метриках. Сломанный интерфейс виден мгновенно и визуально — съехавшая вёрстка, нечитаемый текст, пропавшая кнопка оформления заказа. Поэтому UI-флаги требуют более консервативного темпа rollout и обязательного визуального контроля на каждом шаге, а не только проверки серверных метрик ошибок.

Зачем выпускать интерфейс по частям, а не одним релизом

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

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

Виды rollout для интерфейса

Kill switch — мгновенно откатить новый UI

Самый простой вид флага — бинарный переключатель на уровне всего приложения или одной страницы: интерфейс либо старый, либо новый, без промежуточных состояний. Его роль — не постепенный rollout, а аварийный рубильник: если после полного включения нового интерфейса метрики резко просели, kill switch возвращает всех к старой версии за секунды, без участия разработчика с доступом к серверу деплоя.

Процентный rollout — 10% → 50% → 100%

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

Канареечный релиз для дизайна — шкала 1% → 5% → 20% → 50% → 100%

Для рискованных изменений интерфейса — полного редизайна ключевого экрана, смены воронки оформления заказа — шаг rollout делают более мелким на старте и фиксируют конкретную метрику, которую проверяют перед каждым переходом на следующую ступень шкалы: обычно это конверсия целевого действия и доля пользователей, вернувшихся на поддержку с жалобой на интерфейс. Первый шаг в 1% не про статистическую значимость — такой объём трафика ещё ничего не докажет, — а про то, чтобы явный баг вёрстки увидел минимум людей, прежде чем автоматика или дежурный остановят раскатку.

Где живёт логика UI-флага

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

Где считается флагЗадержка перед рендеромКонсистентность между сессиямиКогда подходит
Клиент (браузер)Минимальная, но возможно мигание версии при повторной загрузке данныхНизкая без внешнего хранилища — значение легко потерять между вкладкамиКосметические эксперименты без влияния на SEO и первый рендер
Edge / CDNНизкая — решение принимается до того, как HTML уйдёт браузеруВысокая, если edge-слой хранит привязку пользователя к вариантуRollout, где важен первый отрисованный кадр без мигания
Бэкенд (сервер приложения)Выше, но в том же запросе, что и остальные данные страницыВысокая — один источник истины для всех клиентов пользователяRollout, где версия интерфейса завязана на серверные данные или права доступа

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

Пример на практике

Упрощённо компонент-обёртка, который решает, какую версию интерфейса отрисовать, выглядит так — без привязки к конкретному фреймворку:

function renderDashboard(user) {
  const variant = featureFlags.getVariant('new-dashboard-ui', user.id);
  // variant вычисляется один раз на основе детерминированного
  // хеша user.id и текущего процента rollout флага

  if (variant === 'new') {
    return renderNewDashboard(user);
  }
  return renderLegacyDashboard(user);
}

Ключевой момент — getVariant не делает случайный выбор при каждом вызове: хеш от идентификатора пользователя и процента rollout гарантирует, что один и тот же человек стабильно попадает в одну и ту же группу, пока процент не изменился. Это и отличает управляемый rollout от случайного A/B-разброса, который ломает пользовательский опыт при каждой перезагрузке страницы.

Типы флагов и когда какой использовать

Не каждый флаг решает одну и ту же задачу — и не каждый тип одинаково хорошо подходит для UI-rollout.

Тип флагаЗадачаПодходит для UI-rollout
Release flagПостепенно открыть готовую фичу — в том числе новый интерфейсДа — основной тип для поэтапного выпуска UI
Experiment flagСравнить метрики двух версий на контролируемых группахДа, с оговоркой — нужна статистически корректная группировка, а не просто процент
Ops flagАварийно отключить нагруженную или нестабильную часть системыС оговоркой — полезен как kill switch для нового UI, но не для постепенного rollout
Permission flagПоказать функциональность в зависимости от плана или роли пользователяНет — решает про доступ, а не про поэтапность выпуска одного и того же интерфейса всем

Для интерфейсного rollout в подавляющем большинстве случаев нужен release flag — постоянно живущий недолго, от старта раскатки до полного перехода на новую версию. Experiment flag пригождается, когда вопрос не «выпускать или нет», а «какая версия лучше» и решение принимается статистически, а не продуктовым чутьём.

Технический долг флагов — кто за это платит

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

Правило «у каждого флага есть владелец и дата удаления»

Рабочая практика — заводить флаг не абстрактно, а с двумя обязательными полями: кто отвечает за решение о полном включении и к какой дате флаг и старая ветка кода должны быть удалены. Дата не обязана быть жёсткой, но её отсутствие на практике означает, что уборка не произойдёт никогда — текущие задачи всегда кажутся важнее удаления давно работающего кода. Отдельно стоит вопрос самого дизайна интерфейса, который rollout обслуживает: если команда, проектирующая интерфейс и помогающая поэтапно вводить его в прод, с самого начала закладывает чёткую точку, когда старая версия компонента считается устаревшей, у разработки появляется формальный повод закрыть флаг, а не откладывать уборку бесконечно.

Чек-лист перед тем, как включить флаг на 100%

  1. Метрика успеха нового интерфейса определена и измеряется автоматически, а не оценивается на глаз
  2. Есть план отката — конкретное значение флага, к которому возвращаются при проблеме, а не «разберёмся по ситуации»
  3. Мониторинг ошибок и производительности разбит по варианту флага, а не показывает общий график по всем пользователям сразу
  4. Тестовая группа на каждом шаге шкалы репрезентативна — не состоит только из внутренних пользователей или одного региона
  5. Автотесты в CI прогоняют обе версии интерфейса, пока флаг активен
  6. Определён порог остановки — конкретное число, при котором раскатка прекращается автоматически, а не по решению дежурного в моменте
  7. У флага назначен владелец и ориентировочная дата удаления после полного rollout

Частые вопросы

Чем feature flag отличается от A/B-теста?

Feature flag — это механизм показа: переключатель, который решает, какую версию интерфейса получит пользователь, и его можно выключить одним действием. A/B-тест — это методология измерения: сравнение метрик между группами с заранее посчитанной статистической значимостью. На практике A/B-тест интерфейса почти всегда реализован через feature flag, но не каждый flag — это тест: kill switch или rollout на 100% без контрольной группы тестом не являются.

Можно ли использовать feature flags без отдельного сервиса управления?

Да, для небольшого проекта достаточно конфигурационного файла или таблицы в базе данных с процентом и списком исключений — такой self-hosted вариант не требует внешнего SaaS. Выделенный сервис (Unleash, Flagsmith, LaunchDarkly) нужен, когда флагов становится десятки, нужен аудит изменений и возможность переключать флаг без деплоя прямо из интерфейса для нетехнической команды.

Как flags влияют на производительность рендера интерфейса?

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

Что делать, если флагов стало слишком много и в коде хаос?

Завести реестр флагов с обязательными полями: владелец, дата создания, дата плановой очистки. Флаг, который давно раскатан на 100% и не откатывался ни разу за квартал, — кандидат на удаление вместе с веткой кода для старой версии. Подробный разбор видов флагов, их хранения и тестирования — в материале «Фича-флаги на практике».

Нужно ли тестировать обе версии UI в CI?

Да, пока флаг активен и показывает обе версии реальным пользователям — автотесты должны прогонять оба состояния: флаг включён и выключен. Если тестировать только одно состояние, вторая ветка кода выпадает из-под защиты тестов именно в тот период, когда на ней находится часть продакшен-трафика.

Как быстро откатить неудачный UI-rollout?

Если rollout организован через feature flag, откат — это изменение значения флага на сервере конфигурации, без повторного деплоя кода: процент пользователей, видящих новый интерфейс, возвращается к нулю за секунды. Это и есть главное практическое преимущество флагов перед git-веткой, которая правит прод напрямую через деплой.

Кто должен решать процент rollout — разработка, продукт или дизайн?

Техническую часть — скорость увеличения процента и порог остановки по ошибкам — задаёт разработка. Но какую метрику считать критерием успеха нового интерфейса и когда можно переходить к следующему шагу шкалы, решает тот, кто отвечает за продуктовый результат: обычно продукт совместно с дизайном, который спроектировал состояния компонента для rollout заранее. Подробный разбор термина есть и у Мартина Фаулера, который ввёл само понятие feature toggle.

Код, который не стыдно передать

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

Случайный разбор