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

Фича-флаг решает конкретную проблему: отделить деплой кода от включения его поведения. Код можно смержить в основную ветку и выкатить в прод, но пользователи его не увидят, пока флаг выключен. Это звучит просто, и первый флаг в проекте обычно действительно просто завести. Проблема начинается через полгода, когда в кодовой базе накопилось сорок флагов, половина из них условия внутри условий, а никто не помнит, какие из них уже можно удалить, потому что фича давно раскатана на сто процентов.
Зачем вообще нужны флаги, если есть git и ветки
Ветки решают задачу изоляции кода на этапе разработки, но не решают задачу изоляции поведения в проде. Если ветка долго живёт отдельно от основной, слияние становится всё болезненнее: чем дольше откладывается интеграция, тем выше шанс конфликтов и тем позже команда узнаёт о проблемах совместимости с остальным кодом. Фича-флаги позволяют интегрировать код рано и часто, при этом не показывая пользователю недоделанную функциональность.
Второе применение, не менее важное: аварийный выключатель. Если новая логика начинает вести себя странно в проде, флаг позволяет вернуться к старому поведению за секунды, без релиза и без откатa деплоя. Это особенно ценно для изменений в критичных путях, таких как оплата или авторизация, где откат через полноценный деплой может занять десятки минут.
Виды флагов: не все флаги одинаковы
Путаница в флагах чаще всего возникает из-за того, что все флаги в проекте живут в одном списке и по одним правилам, хотя у них разное назначение и разный жизненный цикл. Разделение на виды снимает большую часть путаницы.
| Вид флага | Назначение | Типичный срок жизни |
|---|---|---|
| Релизный | Скрыть недоделанную фичу до готовности | недели, удаляется после полной выкатки |
| Операционный | Аварийный выключатель для рискованной логики | живёт долго, иногда постоянно |
| Экспериментальный | A/B-тест, сравнение вариантов поведения | пока идёт эксперимент, затем удаляется |
| Разрешительный | Доступ по подписке, роли или тарифу | постоянный, часть бизнес-логики |
Релизные и экспериментальные флаги почти всегда временные: у них есть естественный момент смерти, когда фича выкатана на всех или эксперимент завершён. Операционные и разрешительные флаги по своей природе постоянные: их не нужно удалять, они часть архитектуры. Путаница возникает, когда временный релизный флаг начинает жить как постоянный, потому что никто не назначил дату его удаления.
Выкатка на процент пользователей: от нуля до ста без скачка
Постепенная выкатка снижает риск: вместо того чтобы включить новую логику всем пользователям одновременно, флаг открывает её сначала одному процента аудитории, затем пяти, затем двадцати, и так далее, с паузами между шагами для наблюдения за метриками. Если на каком-то шаге метрики ошибок или производительности ухудшаются, выкатку останавливают и откатывают именно на этом шаге, не трогая остальных пользователей.
function isFeatureEnabled(userId, rolloutPercent) {
// стабильный хэш от userId даёт одному пользователю
// одинаковый результат на всех запросах, без мигания фичи
const bucket = hashToBucket(userId); // число от 0 до 99
return bucket < rolloutPercent;
}
// пример: 20% пользователей видят новую логику оформления заказа
if (isFeatureEnabled(user.id, 20)) {
renderNewCheckout();
} else {
renderLegacyCheckout();
}Важная деталь в примере выше: хэширование по стабильному идентификатору пользователя, а не случайное число при каждом запросе. Если пользователь на одном запросе попадает в новую версию, а на следующем в старую, это не постепенная выкатка, а источник непредсказуемых багов и путаницы в поддержке. Один пользователь должен стабильно видеть один и тот же вариант на протяжении всего эксперимента.
Где хранить флаги и как их проверять
Для небольшого проекта флаги можно хранить в конфигурационном файле или переменных окружения, и этого достаточно, если флагов немного и переключать их можно через деплой. Как только нужно включать флаг мгновенно, без деплоя, и по разным условиям, таким как сегмент пользователей или регион, нужна отдельная система: от простой таблицы в базе данных с админкой поверх до специализированного сервиса управления флагами.
- Конфиг-файл или переменные окружения: просто, но требует деплоя для переключения
- Таблица в базе с кэшем в приложении: мгновенное переключение, нужна своя админка
- Внешний сервис управления флагами: готовые таргетинг и аналитика, добавляет зависимость
Независимо от хранилища, у каждого флага должен быть явный владелец, то есть человек или команда, которая знает, зачем флаг существует и когда его можно удалить. Без владельца флаг превращается в анонимный код, который никто не решается трогать.
Как не накопить мёртвые флаги
Мёртвый флаг это флаг, который давно включён (или давно выключен) на сто процентов, но условная логика вокруг него всё ещё лежит в коде. Такие флаги загрязняют кодовую базу: разработчик, читающий метод с пятью вложенными проверками флагов, не может быстро понять, какая из веток реально выполняется в проде.
- При создании временного флага сразу фиксировать дату или условие удаления, не оставлять это на потом
- Через ревью кода: автоматическая проверка, которая находит флаги старше установленного срока, например тридцати дней, и напоминает о них в отдельной задаче
- После выкатки фичи на сто процентов и недели стабильной работы удалять не только флаг, но и ветку кода со старым поведением
- Периодически смотреть список всех активных флагов целиком: список длиннее пятнадцати пунктов обычно сигнал, что часть из них забыли убрать
Тестирование кода с флагами
Код с условием по флагу фактически превращается в два разных пути выполнения, и оба пути нужно покрывать тестами, а не только тот, который активен по умолчанию. На практике это означает, что тесты должны явно включать и выключать флаг, а не полагаться на его текущее значение в конфигурации окружения, где запускаются тесты. Иначе через несколько месяцев, когда флаг давно переключён в проде, в тестах тихо проверяется уже неактуальный путь, и регрессия в реальном поведении остаётся незамеченной.
test('оформление заказа работает в новой версии checkout', () => {
const flags = { newCheckout: true };
const result = renderCheckout(cart, flags);
expect(result).toContain('new-checkout-form');
});
test('оформление заказа работает в старой версии checkout', () => {
const flags = { newCheckout: false };
const result = renderCheckout(cart, flags);
expect(result).toContain('legacy-checkout-form');
});Отдельная ловушка: сочетание нескольких флагов одновременно. Если в системе одновременно активны три независимых флага, теоретически получается восемь комбинаций их значений, и не все из них реально встречаются в проде. Разумная стратегия: явно решить, какие комбинации поддерживаются, а какие считаются невозможными в силу бизнес-логики, и тестировать только реалистичные случаи, а не весь комбинаторный перебор.
Кто принимает решение о проценте и темпе выкатки
Отдельный организационный вопрос, который часто упускают: кто именно решает, когда переходить с одного процента на следующий. Если это решение принимает только автор кода, выкатка превращается в его личную ответственность и личный риск, а остальная команда узнаёт о проблемах постфактум. Удобнее заранее договориться о критериях перехода: какие метрики смотрим, сколько времени держим каждый шаг и кто имеет право откатить флаг назад при первых признаках проблемы, причём это право должно быть не только у автора, но и у дежурного инженера.
Фича-флаги дают реальную гибкость: ранняя интеграция кода, безопасная постепенная выкатка и аварийный откат без деплоя. Но эта гибкость не бесплатна, она требует дисциплины: различать виды флагов по назначению, назначать владельца и срок жизни каждому временному флагу, и регулярно чистить то, что уже выполнило свою роль. Команда, которая соблюдает эту дисциплину, получает скорость без накопления технического долга.
Частые вопросы
Сколько фича-флагов одновременно это нормально для среднего проекта?
Жёсткого числа нет, но если список активных временных флагов превышает десять-пятнадцать, скорее всего часть из них уже можно удалить, а команда просто не следит за списком.
Можно ли использовать один и тот же флаг для A/B-теста и для аварийного отключения?
Технически можно, но лучше не смешивать: у экспериментального флага есть момент завершения эксперимента, а аварийный выключатель должен жить дольше и не зависеть от результатов теста.
Нужна ли отдельная система управления флагами с первого дня проекта?
Нет, для старта достаточно конфигурации или переменных окружения. Отдельный сервис оправдан, когда появляется потребность в мгновенном переключении без деплоя и в таргетинге по сегментам.



