Uma regra de negócio costuma chegar em frases separadas. “Cliente premium tem frete grátis.” “Compras acima de R$ 200 também.” “Região remota sempre paga frete.” Cada frase parece simples. O problema aparece quando o mesmo pedido satisfaz as três ao mesmo tempo.
É comum descobrir essa contradição dentro de uma sequência de if, quando a ordem das condições já escolheu uma resposta sem ninguém perceber. Uma tabela de decisão força a conversa a acontecer antes: quais dados participam, quais combinações existem e qual resultado vale em cada uma.
O código não criou a ambiguidade
Considere uma implementação escrita na ordem em que os requisitos chegaram:
function calculateShipping(order) {
if (order.customer.isPremium) return 0;
if (order.total >= 200) return 0;
if (order.address.isRemote) return 39.90;
return 19.90;
}
Uma compra premium para região remota recebe frete grátis porque a função retorna antes de verificar o endereço. Trocar a terceira condição para o topo produz o resultado oposto. As duas versões são coerentes com partes do texto, mas nenhuma prova qual prioridade o produto desejava.
Esse é o primeiro ganho da tabela: separar conflito de requisito de bug de implementação. O desenvolvedor não precisa adivinhar qual frase vence. Ele pode apontar a combinação exata e pedir uma decisão.
Condições de um lado, resultado do outro
A notação DMN, padronizada pela Object Management Group, descreve tabelas de decisão como conjuntos de entradas, saídas e regras. Não é necessário adotar uma plataforma DMN para aproveitar o raciocínio. Uma planilha ou tabela no pull request já resolve boa parte do problema.
| Premium | Total ≥ R$ 200 | Região remota | Resultado decidido |
|---|---|---|---|
| Não | Não | Não | Frete normal |
| Não | Sim | Não | Grátis |
| Sim | Não | Não | Grátis |
| Sim | Sim | Não | Grátis |
| Não | Não | Sim | Frete remoto |
| Não | Sim | Sim | Frete remoto com desconto |
| Sim | Não | Sim | Frete remoto com desconto |
| Sim | Sim | Sim | Frete remoto com desconto |
A tabela acima já contém uma decisão que não existia nos requisitos originais: benefício reduz o frete remoto, mas não o zera. O importante não é esse resultado específico. É tornar a prioridade visível e revisável por produto, suporte, desenvolvimento e testes.
Comece com a tabela completa
Três condições booleanas produzem oito combinações. Quatro produzem 16; dez já produzem 1.024. Esse crescimento é incômodo, mas útil no começo: ele revela o custo lógico que estava escondido no texto. Se a tabela explode, talvez a regra esteja tentando decidir coisas demais de uma vez.
Depois de preencher as combinações, é possível condensar linhas. Na região não remota, por exemplo, quando o total passa de R$ 200, ser premium deixa de alterar o resultado. O valor pode virar “tanto faz”. A especificação DMN usa um traço para representar uma entrada irrelevante naquela regra.
Não elimine uma combinação apenas porque parece estranha. Primeiro classifique: é possível, impossível por construção ou apenas não prevista? “Usuário bloqueado e sessão ativa” pode soar impossível, mas talvez ocorra entre a revogação da conta e a expiração do token. Estados distribuídos têm talento para visitar cantos que um quadro branco considerou absurdos.
Cada linha pode virar um teste
Tabelas de decisão também são uma técnica de projeto de testes. A referência do ISTQB recomenda ao menos um caso por regra da tabela. Isso cria uma ligação direta entre a decisão discutida e a suíte que protege sua implementação.
const cases = [
[{ premium: false, total: 120, remote: false }, 19.90],
[{ premium: false, total: 220, remote: false }, 0],
[{ premium: true, total: 120, remote: true }, 24.90],
[{ premium: false, total: 220, remote: true }, 24.90],
];
test.each(cases)('calcula a regra de frete', (input, expected) => {
expect(calculateShipping(input)).toBe(expected);
});
A suíte não precisa repetir linhas equivalentes depois que a equipe comprovou que uma condição é irrelevante. Mas essa redução deve ser uma conclusão da tabela, não um palpite para escrever menos testes.
A ordem dos ifs deixa de ser a regra
Com as prioridades resolvidas, o código pode refletir o domínio. Primeiro calcule se existe benefício; depois trate a exceção geográfica. Outra opção é representar cada regra como dado e procurar uma correspondência. Para meia dúzia de regras, uma função explícita costuma ser mais legível que construir um motor genérico.
Também vale nomear conceitos. qualifiesForFreeShipping comunica melhor que repetir premium || total >= 200 em vários lugares. Se a negação começa a acumular !, && e ||, as leis de De Morgan ajudam a simplificar, mas um nome de domínio ainda é mais fácil de revisar que uma expressão logicamente elegante.
Quando a técnica se paga
- Permissões que combinam papel, propriedade, estado e contexto.
- Descontos, impostos, frete e limites comerciais.
- Validações com exceções e prioridades.
- Fluxos de aprovação, risco e prevenção de fraude.
- Feature flags que interagem entre si.
Uma condição simples não merece uma tabela cerimonial. Use a técnica quando o resultado depende da combinação de fatores ou quando duas pessoas interpretam a mesma frase de maneiras diferentes. Nesse ponto, alguns minutos preenchendo células custam menos que descobrir em produção qual if ganhou pela posição.
Defina a política quando mais de uma linha casa
Uma tabela pode exigir correspondência única, primeira correspondência, coleta de resultados ou prioridade explícita. Sem essa política, duas linhas verdadeiras devolvem a ambiguidade que a técnica deveria remover. Para regras de preço ou permissão, “primeira linha vence” também transforma a ordem do arquivo em comportamento de negócio.
function decidir(contexto, regras) {
const aplicaveis = regras.filter((regra) =>
regra.condicoes.every((condicao) => condicao(contexto)),
);
if (aplicaveis.length !== 1) {
throw new Error(`Esperava 1 regra, encontrei ${aplicaveis.length}`);
}
return aplicaveis[0].resultado;
}
Gerar testes a partir das linhas cobre exemplos conhecidos. Acrescente propriedades que protejam o espaço entre elas: todo contexto válido encontra resultado, nenhuma combinação crítica encontra duas regras e aumentar uma quantidade não reduz um desconto quando essa monotonicidade faz parte do negócio.
Quando uma correção revela uma combinação ausente, vale registrar o caso como teste de regressão. Se a regra nova será exposta gradualmente, uma feature flag controla o rollout, mas não deve esconder contradições na tabela.
