Когда простого true/false недостаточно: размещение feature-флагов в коде

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

Здесь на помощь приходят feature-флаги. Они позволяют разделить деплой и релиз: вы можете выложить новый код в продакшен, не делая его видимым для всех. Но как именно разместить эти флаги в коде, чтобы не превратить его в мешанину из запутанных условий?

Простейший флаг: булевый переключатель

По своей сути feature-флаг — это просто условное ветвление. Если флаг включён, выполняется новый код. Если выключен — старый. Базовый вариант выглядит так:

if fiturXEnabled:
    show_new_feature()
else:
    show_old_feature()

Здесь fiturXEnabled — булева переменная. Установите её в True — пользователи увидят новую функцию. Установите в False — увидят старую. Просто.

Но есть нюанс: значение флага должно быть определено до выполнения кода. Если вы хотите менять его без переразвёртывания, нужно читать значение откуда-то извне самого кода. Это может быть конфигурационный файл, переменная окружения или удалённый сервис. Удалённое управление флагами мы рассмотрим позже, а пока сосредоточимся на том, когда простого булева флага достаточно.

Булев флаг хорошо работает, когда функция небольшая и вам нужно всего два состояния: включено или выключено. Например, представьте кнопку «Печать отчёта», которая раньше была доступна только администраторам. Теперь вы хотите открыть её для всех пользователей. Вы добавляете флаг printReportForAllUsers. Когда он True, кнопка появляется у всех. Когда False — только у администраторов. Готово.

Когда простого недостаточно: условные флаги

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

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

if fiturXEnabled and user.id in trial_user_list:
    show_new_feature()
else:
    show_old_feature()

Это работает, но trial_user_list нужно управлять отдельно. Если список часто меняется, потребуется способ обновлять его без переразвёртывания. Это добавляет сложности.

Более чистый подход — использовать провайдер флагов. Провайдер флагов — это функция или библиотека, которая принимает контекст (например, ID пользователя, регион, тип устройства) и возвращает значение флага на основе заданных правил. Ваш код становится таким:

if flag_provider.is_enabled("featureX", user=current_user):
    show_new_feature()
else:
    show_old_feature()

За кулисами провайдер флагов проверяет правила: находится ли пользователь в тестовой группе? Совпадает ли его регион? Достигнут ли процент трафика? Вам не нужно переписывать эту логику каждый раз при использовании флага.

Поддержание чистоты кода

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

Несколько принципов помогут:

Один флаг — одна ответственность. Не пихайте несколько условий в один флаг. Если нужно управлять видимостью по региону и по роли пользователя, используйте два отдельных флага. Это понятнее и проще удалить позже.

Размещайте флаги на правильном уровне. Помещайте флаг как можно ближе к точке входа функции. Для UI-компонента это может быть функция рендеринга. Для API-эндпоинта — обработчик. Избегайте разбрасывания одного и того же флага по разным слоям кода.

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

Практический чек-лист

Прежде чем написать очередной feature-флаг, пройдитесь по этим пунктам:

  • Можно ли управлять этой функцией простым булевым флагом, или нужна условная логика?
  • Откуда будет браться значение флага? Жёстко закодировано, конфигурационный файл, переменная окружения или удалённый сервис?
  • Размещён ли флаг на правильном уровне, близко к месту использования функции?
  • Имеет ли флаг одну чёткую ответственность?
  • Спланировали ли вы, как удалить флаг и старый путь кода позже?

Что дальше

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

Но всё это не имеет значения, если ваши флаги грязные. Начните с чистых, правильно размещённых флагов в коде. Остальное придёт само.