ADR 4 — Uma implementação por canal, não um motor parametrizado
O problema
Cálculo de comissão para canais que remuneram de formas diferentes: varejo físico, e-commerce, atacado, pessoa jurídica e operação em outro país. Olhadas de longe, as regras são a mesma coisa: percentual sobre venda, ajustado por devolução, distribuído por hierarquia. De perto, divergem em tudo que importa — base de cálculo, momento de apuração, o que retroage, como o rateio trata equipe, o que acontece quando a venda é cancelada depois do fechamento.
E é cálculo com consequência dos dois lados: para menos, o vendedor reclama com razão; para mais, a empresa paga o que não devia. Precisa ser auditável, porque alguém vai perguntar por que aquele valor deu aquilo, meses depois.
Onde este caso difere de pagamentos
Esta é a parte da decisão que me deu trabalho, e é o que a torna interessante: eu tinha acabado de resolver um problema parecido do jeito oposto.
Em ADR 1 — Política de risco em dado, não em código eu tirei a política do código e coloquei em configuração, e funcionou. A tentação de repetir a receita era grande, e teria sido um erro.
A diferença é o que varia. Em pagamentos, o que varia é quais etapas de uma sequência conhecida se aplicam: um espaço fechado, descritível por indicadores. Em comissão, o que varia é a fórmula. Parametrizar sequência funciona. Parametrizar fórmula produz uma linguagem de programação mal disfarçada de configuração, e o resultado é o pior dos dois mundos: o negócio não consegue mexer porque virou programação, o desenvolvedor mexe com medo porque não parece código, e ninguém tem teste porque não é nem uma coisa nem outra.
Entender por que o padrão que eu mesmo tinha estabelecido não se aplicava aqui foi o ponto da decisão. Padrão bom aplicado no lugar errado faz mais estrago que ausência de padrão, porque vem com autoridade.
Por que herança também não
O caminho do meio seria uma classe base com pontos de extensão por canal: o esqueleto do algoritmo no pai, cada passo variando no filho. Descartei porque template method engessa a ordem.
Funciona enquanto todo canal segue a mesma sequência de passos. O primeiro canal que precisa inverter dois passos, ou pular um, vira uma briga com a classe base — e a saída usual dessa briga é um parâmetro de controle no pai, que é exatamente o motor parametrizado com passos extras.
A decisão
Cada canal tem seu cálculo, independente, legível de cima a baixo e testável isoladamente. O que é genuinamente comum é extraído para utilitário, não para hierarquia.
A duplicação que eu aceitei
É real e não tem disfarce. Corrigir um comportamento compartilhado exige aplicar em cinco lugares, com risco permanente de corrigir em quatro e esquecer o quinto. E não há um lugar único que responda “qual é a regra de comissão” — a resposta é sempre “de qual canal?”.
Aceitei porque a alternativa troca duplicação por acoplamento, e acoplamento em cálculo financeiro é mais caro: um cálculo genérico que atende cinco canais é um cálculo que ninguém altera sem regredir os outros quatro.
Como envelheceu
Bem, por uma razão que eu não tinha antecipado: rotatividade. O maior repositório da área passou por 29 autores diferentes ao longo do tempo, e código independente e legível sobreviveu a isso. Cada pessoa que chegou conseguiu alterar o canal de que precisava sem entender os outros quatro.
Um motor genérico com essa quantidade de mãos teria virado código intocável — daqueles que todo mundo contorna em vez de alterar. Eu não decidi pensando em rotatividade; foi ela que validou a decisão.
O que eu faria diferente
Extrair a base comum mais cedo. Rateio proporcional, tratamento de resíduo de arredondamento e trilha de apuração são genuinamente iguais entre canais, e ficaram duplicados por tempo demais porque eu apliquei “não generalizar” de forma larga demais. A regra que eu sigo hoje, extrair no terceiro caso e não antes, teria dado o ponto certo aqui.
Ver também
Comissionamento · ADR 1 — Política de risco em dado, não em código · Como eu trabalho