Что такое feature toggle или как избавиться от мучительных мёржей и долгоживущих веток?

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

Проблема

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

  • Создать отдельную ветку и в течение трёх месяцев выполнять в ней всю работу, периодически делая pull из родительской ветки
  • Использовать концепт непрерывной интеграции (Continious Integration или коротко CI): декомпозировать задачу и мёржить код небольшими порциями

Оба этих подхода имеют свои вполне очевидные достоинства и недостатки:

Достоинства
Недостатки

Долгоживущая ветка
Код вашей неготовой фичи не будет находиться в родительской ветке и не будет мозолить глаза другим разработчикам

  • Постоянные merge-конфликты, которые вам потребуется разрешать
  • Монструозный pull request, непригодный для code-review

Много коммитов из веток, с коротким сроком жизни
Задача хорошо декомпозирована и мёржится небольшими порциями, нет необходимости разрешать merge-конфликты
Ваша неготовая фича будет нервировать других разработчиков и, возможно, вызывать сторонние эффекты на UI

Использование feature switcher-ов для решения проблем

Такая проблема встречается в разработке довольно часто и есть изящное решение, позволяющее взять лучшее от описанных выше подходов — feature toggle или feature switcher.
По сути, feature switcher — это boolean флаг, который хранится в базе данных и содержит информацию о том, должна быть включена та или иная фича или нет. Значение этого флага может быть извлечено из базы данных по ключу. Удобство использования feature switcher-ов заключается в том, что они могут быть легко изменены бизнес-пользователем во время runtime через панель администратора без необходимости заново деплоить приложение.
Ниже приведен пример использования feature toggle на языке Java:

if (configurationManager.getParameter("is.some.functionality.enabled")) {
    // do some stuff
} else {
    // do default logic
}

В примере выше configurationManager — это класс, позволяющий извлечь значение определенного feature switcher-а из базы данных по его ключу.
Также, при помощи feature switcher-ов, можно отображать/скрывать определенные элементы на фронтенде. Для этого придется положить значение флага в Model и передать его на View как это показано ниже:

@ModelAttribute("isSomeFunctionalityEnabled")
public void isSomeFunctionalityEnabled() {
    return configurationManager.getParameter("is.some.functionality.enabled");
}

После чего использовать переданное значение для рендеринга того или иного HTML кода:

<c:choose>
  <c:when test="${isSomeFunctionalityEnabled}">
    <!-- Render some stuff -->
  </c:when>
  <c:otherwise>
    <!-- Render some other stuff -->
  </c:otherwise>
</c:choose>
Виды feature switcher-ов

Описанный концепт использования feature switcher-ов — это лишь один возможный случай использования и такие feature switcher-ы называются release toggles. Всего выделяют 3 разных вида feature switcher-ов:

  • release toggles — позволяют скрывать не до конца имплементированные фичи во время их разработки
  • experiment toggles — переключатели для A/B-тестирования
  • permissioning toggles — включатели/выключатели фич для различных групп пользователей

Таким образом, используя feature switcher-ы, можно построить две различные версии сайта на одной кодовой базе, используя разные БД и разные наборы feature switcher-ов. Например, на европейском сайте имеет смысл включать все фичи, имеющие отношение к GDPR, а на российском этого можно и не делать.

Проблемы использования feature toggle-ов

Поскольку я работаю на проекте, где активно используются feature toggle-ы, то кроме очевидных достоинств их использования я начал замечать и проблемы, связанные с ними:

  • Сложность тестирования: при выходе нового релиза, QA инженеры тестируют все фичи, которые в него входят, а также пробуют включать и выключать их, используя feature switcher-ы. Это требует большого количества дополнительного времени, так как желательно протестировать всевозможные комбинации флагов
  • Появление мёртвого кода: значения многих feature toggle-ов не меняются на протяжении длительных промежутков времени или не меняются вовсе, и таким образом код, написанный для другого значения флага, фактически становится «мёртвым»
  • Неожиданные поломки сайта: многие из устаревших feature switcher-ов имеют досадное свойство ломать что-либо при изменении своего значения (так как никто давно не проверял, что они работают). Так как feature switcher-ы хранятся в БД и могут быть легко изменены бизнес-пользователями из панели администратора, часто происходят поломки, вызванные изменением их значения. Работоспособность давно неиспользованных feature switcher-ов стоит сперва проверить на тестовом окружении
Решения некоторых из описанных проблем

Помочь решить вышеописанные проблемы могут следующие действия:

  • Документирование имеющихся feature switcher-ов: чтобы понимать, какой эффект имеет тот или иной feature toggle и по какому ключу его искать в базе данных, следует создать подробную документацию с описанием всех feature switcher-ов.
  • Периодическая ревизия feature switcher-ов: чтобы избежать появления “мёртвого” кода, нужно периодически удалять устаревшие feature switcher-ы и связанный с ними код
Итоги

Feature switcher — очень простой и одновременно мощный механизм, позволяющий избегать монструозных коммитов, легко менять поведения приложения или собирать несколько разных приложений на одной кодовой базе, используя разную конфигурацию feature toggle-ов.
Однако, стоит также помнить, что этот паттерн разработки имеет некоторые недостатки, которые выливаются в трудночитаемый и трудно поддерживаемый код, поэтому следует избегать чрезмерного использования этого паттерна и периодически проводить документирование feature switcher-ов и их ревизию, чтобы удалять неиспользуемые и, как следствие, очищать проект от “мёртвого” кода.