В этой статье мы расскажем про принципы безопасной работы с переключателями функционала – 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)  позволяют задавать списки файлов, нуждающихся в компиляции и линковке для генерации итогового исполнимого файла приложения. Такие списки можно дополнять и сокращать в соответствие с какими-то условиями или настройками сборки проекта. 

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

Комментарии (0)