Feature Flags: Como Separar Deploy de Release e Entregar Sem Medo
Feature flags desacoplam colocar código em produção de ativá-lo para o usuário. Entenda os tipos de flag, rollout progressivo, kill switch e como não afundar em dívida técnica de flags.
Neste artigo
Existe uma diferença que muda a forma como um time entrega software, e que a maioria descobre tarde demais: deploy e release não são a mesma coisa. Deploy é colocar o código em produção. Release é torná-lo visível e ativo para o usuário. Quando esses dois eventos acontecem juntos — o código sobe e no mesmo instante está no ar para todo mundo — cada deploy é uma aposta de tudo ou nada. É aí que entram as feature flags.
Uma feature flag (ou feature toggle) é um interruptor no código que decide, em tempo de execução, se um trecho de funcionalidade está ativo ou não, sem exigir um novo deploy para mudar. Com ela, você faz o deploy do código desligado, e depois liga a funcionalidade quando quiser, para quem quiser — e desliga na mesma velocidade se algo der errado. Vamos entender os tipos de flag, os padrões de uso e as armadilhas que fazem times bem-intencionados afundarem em dívida técnica.
O conceito central: o interruptor em runtime
No código, uma flag é uma condição:
``python if flags.is_enabled("novo_checkout", user=usuario): return renderizar_novo_checkout(usuario) return renderizar_checkout_antigo(usuario) ``
O que muda tudo é de onde vem o valor de is_enabled. Ele não é uma constante compilada; é buscado em runtime de um serviço de configuração de flags. Isso significa que você pode alterar o comportamento da aplicação — para todos, para 10% dos usuários, para um cliente específico — sem tocar no código e sem reimplantar. O mesmo artefato em produção passa a se comportar de forma diferente porque a configuração da flag mudou.
Essa separação entre deploy e release é o que destrava várias práticas modernas: integração contínua de verdade (você mescla código incompleto, protegido por flag), rollout progressivo, testes A/B, e a capacidade de desligar uma funcionalidade problemática em segundos.
Os tipos de flag (e por que a distinção importa)
Nem toda flag é igual. Uma classificação clássica separa quatro tipos por longevidade e dinamismo, e confundi-los é a origem de boa parte da bagunça.
Release toggles
São flags temporárias que escondem funcionalidade em desenvolvimento. Permitem mesclar código incompleto na branch principal sem expô-lo. Vivem dias ou semanas — o suficiente para a funcionalidade ficar pronta e ser ativada. Devem ser removidas assim que a feature está 100% no ar. São a fonte número um de dívida quando esquecidas.
Experiment toggles
Dividem usuários em grupos para testes A/B. Um grupo vê a variante A, outro a B, e você mede qual converte melhor. São dinâmicas por usuário e vivem o tempo do experimento. A decisão de qual variante cada usuário vê precisa ser consistente (o mesmo usuário sempre cai no mesmo grupo) para o experimento ter validade.
Ops toggles
Controlam aspectos operacionais em produção: ligar/desligar uma funcionalidade pesada sob carga, ativar um caminho de degradação graciosa. O kill switch é o exemplo mais importante — um interruptor para desativar rapidamente algo que está causando problema. Alguns ops toggles são de vida longa, permanecendo como válvulas de controle permanentes.
Permission toggles
Controlam quais funcionalidades cada usuário ou plano vê — recursos premium, acesso antecipado para beta testers. São de vida longa por natureza, porque fazem parte da lógica de produto, não de entrega. Aqui a "flag" na prática se confunde com autorização e deve seguir as regras de authz (decisão no servidor, nunca confiável no cliente).
A confusão perigosa é tratar um release toggle como se fosse permanente (e nunca removê-lo) ou um permission toggle como temporário. Nomeie e documente o tipo de cada flag desde o nascimento.
Rollout progressivo: ligando aos poucos
O maior ganho operacional das flags é poder ativar uma funcionalidade gradualmente, observando o impacto antes de expor todo mundo. Em vez de um big-bang, você abre a torneira devagar:
``text Dia 1: novo_checkout = 1% dos usuários -> observa erros, latência Dia 2: novo_checkout = 10% -> métricas saudáveis, segue Dia 3: novo_checkout = 50% Dia 4: novo_checkout = 100% ``
Se, em qualquer etapa, as métricas degradarem — taxa de erro sobe, conversão cai, latência dispara — você volta a flag para o percentual anterior instantaneamente. Isso é um rollback sem deploy: o código problemático continua em produção, mas desligado. Compare com o rollback tradicional, que exige reimplantar a versão anterior e leva minutos ou mais.
O direcionamento pode ir além do percentual: ativar primeiro para usuários internos (dogfooding), depois para um beta, depois por região, por plano, por qualquer atributo. A flag vira um bisturi para controlar exatamente quem é exposto ao novo.
O kill switch: sua rede de segurança
Toda funcionalidade nova de risco deveria nascer atrás de um kill switch — uma flag cujo único propósito é desligar aquilo rápido. Quando um alerta dispara às três da manhã por causa da feature que subiu ontem, a diferença entre "desligo a flag em dez segundos" e "preciso preparar, revisar e implantar um rollback" é a diferença entre um soluço e um incidente.
``python # Caminho protegido por kill switch com degradação graciosa. if flags.is_enabled("recomendacoes_ml"): try: return servico_ml.recomendar(usuario) except Exception as e: logger.error("recomendacao_ml_falhou", trace_id=trace_id, erro=str(e)) return recomendacoes_padrao() # degrada, não quebra return recomendacoes_padrao() ``
Repare que o kill switch e a degradação graciosa andam juntos: quando a funcionalidade está desligada (ou falha), o sistema cai num comportamento padrão seguro, em vez de quebrar. É a mesma filosofia da independência de serviços — a ausência de um componente degrada a experiência, não derruba o sistema.
A dívida técnica das flags
Aqui está o lado sombrio. Cada flag adiciona um if no código, e cada if dobra os caminhos possíveis. Um punhado de flags convive bem; dezenas de flags esquecidas transformam o código em um labirinto onde ninguém sabe mais quais combinações são realmente testadas ou usadas. Flags acumuladas são dívida técnica que rende juros compostos.
Os problemas concretos:
- Explosão combinatória: N flags criam até 2^N combinações de estado. Testar
todas é impossível; na prática, você testa só as que imagina, e as outras viram bugs latentes.
- Código morto disfarçado: uma flag que já está em 100% há meses ainda tem o
ramo else com o código antigo, que ninguém remove e que confunde quem lê.
- Flags zumbi: ninguém lembra para que servem, ninguém tem coragem de
removê-las, e elas ficam ali para sempre.
A disciplina que evita isso:
- Toda release toggle nasce com data de validade e um responsável. Se passou
do prazo, vira tarefa de remoção prioritária.
- Remover a flag faz parte de terminar a feature. A funcionalidade só está
"pronta" quando está em 100%, estável, e a flag foi removida junto com o código do caminho antigo.
- Inventário de flags visível. Um painel que lista todas as flags ativas, seu
tipo, idade e estado. Flags velhas demais aparecem em vermelho.
- Limpeza periódica. Reserve tempo de manutenção para caçar e remover flags
mortas, como quem tira o lixo.
Distinguir o tipo ajuda aqui: release toggles têm de morrer; permission e alguns ops toggles são permanentes por design. O problema é quase sempre release toggle que virou permanente por inércia.
Flags e testes: a combinação que precisa ser testada
Um risco pouco discutido é que feature flags multiplicam os estados que o sistema pode assumir, e isso tem consequência direta para os testes. Se uma funcionalidade está atrás de uma flag, tanto o caminho com a flag ligada quanto o desligado precisam funcionar — e ambos precisam de cobertura. Testar só o estado que está em produção hoje deixa o outro caminho como um campo minado: no dia em que você liga a flag, descobre que aquele ramo nunca foi exercitado.
A prática saudável é testar os dois estados relevantes de cada flag ativa, e resistir à tentação de deixar flags acumularem justamente porque cada uma dobra o espaço de teste. Quando uma release toggle chega a 100% e é removida, o número de combinações diminui e o sistema volta a ser testável de forma exaustiva. É mais um argumento para a disciplina de remoção: flags não são só dívida de leitura de código, são dívida de testabilidade.
Onde as flags são avaliadas, e a questão da confiança
Uma decisão de arquitetura importante: a avaliação da flag acontece no servidor ou no cliente? Para qualquer coisa que envolva segurança ou autorização (os permission toggles), a resposta é categórica: no servidor. Uma flag que esconde um botão de admin no frontend é só experiência de usuário — se a rota do servidor não reverifica a permissão, um usuário mal-intencionado chama a API direto e contorna o botão escondido. Flag de UI não é controle de acesso.
Para flags de release e experimento sem implicação de segurança, avaliar no cliente é aceitável e às vezes preferível (evita uma ida ao servidor). Mas o valor que decide precisa vir de uma fonte confiável e ser resolvido de forma consistente por usuário.
Começando com o pé direito
Feature flags são uma dessas ferramentas que resolvem problemas reais e criam outros se usadas sem disciplina. Um caminho pragmático para adotá-las bem:
- Comece com um mecanismo simples de flags — pode ser configuração em runtime
antes de um serviço dedicado — e proteja a próxima funcionalidade de risco atrás de uma.
- Pratique o rollout progressivo já na primeira: 1%, 10%, 50%, 100%, observando
métricas em cada degrau.
- Trate o kill switch como item obrigatório de qualquer feature que possa quebrar
produção.
- Nomeie, documente e datar cada flag desde o commit que a cria.
- Faça da remoção parte da definição de pronto.
O objetivo final não é ter muitas flags — é entregar com segurança. A flag é o meio pelo qual você separa a coragem de implantar da prudência de expor, ligando o novo devagar e podendo voltar atrás num piscar de olhos. Usada com disciplina, ela transforma o deploy de um evento tenso em um não-evento, e é isso que permite a um time entregar muitas vezes ao dia sem prender a respiração a cada push.