В этой статье мы расскажем про принципы безопасной работы с переключателями функционала – Feature Toggles:
Что из себя представляют переключатели функционала и для чего их использовать.
Какие проблемы возникают при неправильном использовании.
Что такое «горячие» и «холодные» переключатели, и как они способны решить проблемы из прошлого пункта.
Реализация «холодных» toggle-ов с помощью условной компиляции и линковки.
Наверняка статья не станет откровением для опытных разработчиков, но пригодится их младшим товарищам.
Что такое Feature toggles и в чём проблема
О том что такое переключатель функционала, можно подробно прочитать в этой статье. Нам лишь остается добавить, что в ПСБ каждый̆ новый̆ функционал обязан быть закрыт переключателем. Однако в вышеуказанном источнике отсутствует одна проблема, возникающая при некорректном использовании механизма переключателей – о ней мы тоже расскажем.
Представим, что мы решили реализовывать долгоиграющий функционал в полном соответствии с CI и Gitlab Flow (в оригинале или на русском). Мы предварительно закрыли переключателями все места, в которые собираемся добавлять наш код, и в них был добавлен новый функционал. По истечении первого спринта функционал все еще не готов полностью, при этом новый код уже в master-ветке. Процесс Gitlab Flow нам этого не запрещает. Текущий master используется для отсечки ветки staging для нового релиза, для примера - под версией 1.1.
Разработка функционала продолжается, но к концу второго спринта он снова не готов. Однако код, закрытый переключателем, лежит в master, из которого и собирается второй релиз под номером 1.2.
Наконец, к завершению третьего спринта наш функционал окончательно готов, проверен, покрыт автотестами, сложен в master. Происходит релиз продукта под версией 1.3, после чего и происходит перевод переключателя функционала во включённое состояние.
В этом момент и возникает проблема.
Так как «новый» код попал с одним и тем же переключателем функционала в релизы 1.1, 1.2 и 1.3, то во всех приложениях этих версий он и включится. Однако следует помнить, что в первых двух версиях наш функционал был еще не закончен и имел лишь частичную реализую. Эта реализация становится доступна пользователям и приводит к ошибкам поведения, крашам, убыткам, недовольству клиентов.
Особенно остро проблема стоит в мобильных устройствах, на которых невозможно в один момент обновить все установленные копии приложения. Поэтому они еще долгое время остаются устаревшими.
Очевидно, что в такой реализации переключатели функционала используются неверно – нарушена обратная совместимость.
«Горячие» и «холодные» переключатели функционала (Hot'n'Cold)
Необходимо различать флаги, которые используются для скрытия незавершенного функционала, от флагов, которые используются для скрытия/показа функционала определенным группам пользователей или A/B тестирования. В процитированной статье про переключатели функционала такие toggle-ы называются соответственно:
release
experiment
permissioning
Первый тип переключателей не должен управляться с сервера во избежание случайного включения неготового функционала. Он должен контролироваться исключительно фронт-разработчиком, реализующим функционал. Вторые, наоборот, – обязаны управляться только с сервера. Поэтому первый тип переключателей называют «холодным» (Cold), а второй и третий объединяют в группу «горячих» (Hot) переключателей.
Можно заметить, что ничто не мешает использовать одновременно и «холодный», и «горячий» токен. Первый включит функционал только в той версии, в которой он будет полностью закончен. При этом при включенном «холодном» переключателе останется возможность управлять функционалом со стороны сервера с помощью горячего токена.
Однако, у простой реализации «холодного» toggle-а в виде конструкции if (featureFlag) { } else {} есть недостаток. Он заключается в том, что код неготового функционала все же попадает в релиз на бой и простым изменением булевого флага злоумышленник может получить доступ к нему. Это можно привести к самым разнообразным последствиям. Также существует риск того, что флажок будет залит на бой с ошибочным значением.
Чтобы исключить перечисленные уязвимости и потенциальные ошибки, мы предлагаем реализовывать «холодные» переключатели с помощью условных компиляции и линковки.
Реализация «холодных» переключателей функционала с помощью условных компиляции и линковки
Условная компиляция
Во многих языках программирования перед компиляцией кода запускается дополнительная программа, называемая препроцессор. Её основной задачей является предварительная обработка программного кода. Одним из примеров такой предварительной подготовки считается в том числе и условная компиляция. Это допускает возможность существования различных версий одного кода. В нашем случае только Dev-сборки приложения будут иметь код с точкой входа в наш функционал, в то время как Test и Prod сборки не смогут его использовать.
Пример, на языке Си с использованием «горячего» (feature1) и «холодного» (DEBUG) переключателей:
#ifdef DEBUG
if (feature1) {
showFiture1()
}
#endif
Представленный код будет компилироваться только в DEBUG-режиме.
Условная линковка
Подход с условной компиляцией убирает только код запуска точки входа в функционал. Сам же функционал все еще компилируется и линкуется с релизным приложением. Было бы неплохо полностью исключить его из релиза без удаления из master-ветки.
Можно, конечно, закрывать весь вновь добавляемый код директивами компилятора #ifdef DEBUG.
Однако есть несколько минусов этого способа:
Очень трудоемко
Снижается читабельность кода
Не все языки программирования имеют возможность условной компиляции
Здесь может понадобиться линковка. Многие менеджеры сборок (например, make или xcodebuild в iOS) позволяют задавать списки файлов, нуждающихся в компиляции и линковке для генерации итогового исполнимого файла приложения. Такие списки можно дополнять и сокращать в соответствие с какими-то условиями или настройками сборки проекта.
Этой возможностью можно пользоваться для исключения файлов из компиляции релизных версий приложений, пока функционал еще не готов. При завершении разработки исключенные файлы просто добавляются в список релизной конфигурации и код начинает работать.