02 - DRY (Don't Repeat Yourself)
video: https://youtu.be/... no frontmatter da nota.DRY (Don't Repeat Yourself)
DRY é o princípio que afirma que cada pedaço de conhecimento de um sistema deve ter uma
representação única, inequívoca e autoritativa. Formulado por Andy Hunt e Dave Thomas em The
Pragmatic Programmer, ele fala de conhecimento, e não de texto: duas rotinas semelhantes
representam duplicação real apenas quando mudam pelo mesmo motivo. Em produção, a violação
tem custo direto: a regra de desconto corrigida em um serviço e esquecida em outro gera
cobrança divergente, ticket de suporte e perda de confiança no produto. O princípio orienta
a extração de módulos compartilhados, a derivação de valores a partir de uma fonte única e a
eliminação de esquemas paralelos que precisam ser sincronizados à mão. Aplicado sem
critério, porém, ele produz acoplamento indevido entre contextos que deveriam evoluir em
separado.
O problema que resolve: divergência
Quando o mesmo conhecimento existe em dois lugares, os dois começam sincronizados e terminam
divergentes. O caso típico é a validação de CPF escrita no frontend, repetida no backend e
copiada para um job de correção de base; o bug descoberto no backend é corrigido lá, e nos
outros dois pontos só depois de um incidente. A divergência raramente é flagrada em code
review, porque os pontos de cópia costumam pertencer a times e repositórios diferentes. O
custo real não está na segunda digitação, que é barata; está na segunda manutenção, que
quase sempre é esquecida. DRY transforma esse risco em regra operacional: identifique o que
muda junto, coloque em um único lugar e derive o resto a partir dele.
Duplicação de conhecimento versus duplicação incidental
Nem tudo que se parece é duplicação. Duplicação de conhecimento ocorre quando dois trechos
de código representam a mesma regra e precisam mudar juntos; duplicação incidental ocorre
quando dois trechos parecem iguais, mas mudam por razões independentes. A validação de CPF e
a validação de CNPJ compartilham a estrutura visual, porém obedecem a órgãos normativos
diferentes e mudam em datas diferentes; fundi-las em uma função com flags cria uma falsa
unificação que trava a evolução das duas. O critério operativo é perguntar se um requisito
novo obrigaria a tocar nos dois pontos ao mesmo tempo. Se a resposta é não, o que existe é
parecença, e forçar uma abstração comum é pior do que manter as duas cópias. Esse
discernimento separa o DRY maduro, que elimina redundância de regra, do DRY ingênuo, que
elimina linhas de código parecidas.
O eixo de variação é o que define a abstração correta. Dois loops sobre coleções não são
duplicação; são a mesma operação elementar da linguagem. Duas consultas que somam
faturamento com filtros diferentes podem ou não ser; se o filtro muda por decisão de negócio
junto com a regra de juros, são a mesma regra com parâmetro; se um filtro pertence ao
jurídico e outro ao comercial, são conhecimentos distintos que coincidem hoje. Ler a
intenção, e não o texto, é a habilidade central aqui.
DRY dentro de um serviço
Dentro de um único serviço, a fonte única de verdade pode ser aplicada de forma agressiva e
barata. Regras de negócio residem em módulos de domínio e são importadas pelos casos de uso;
valores derivados são calculados, e não armazenados duas vezes; tipos são gerados a partir
do esquema da API em vez de reescritos à mão; constantes vivem em um único ponto. O ganho
aparece em cada mudança: ajustar a política de desconto em um arquivo faz checkout,
relatório e job de correção se comportarem de forma idêntica no deploy seguinte.
As aplicações concretas são numerosas. Migrações de banco definem o esquema, e o modelo do
ORM é gerado ou alinhado a partir delas; o contrato OpenAPI define os tipos que os clientes
consomem por geração de código; o pipeline calcula o checksum e não o reescreve; a tabela de
preços alimenta checkout e faturamento a partir do mesmo registro. Cada caso elimina uma
sincronização manual, e sincronização manual é exatamente onde a divergência nasce.
# Fonte única da regra de desconto: vive em um módulo de domínio e é
# importada pelo checkout, pelo relatório financeiro e pelo job de correção.
from decimal import Decimal
LIMITE_PREMIUM = Decimal("8000.00")
def desconto_para(cliente: "Cliente", total: Decimal) -> Decimal:
# Regra definida pelo produto; qualquer alteração acontece aqui,
# uma única vez, e todos os consumidores herdam a política nova.
if cliente.anos_de_casa >= 5:
return total * Decimal("0.05")
if cliente.mensalidade_media > LIMITE_PREMIUM:
return total * Decimal("0.03")
return Decimal("0")
# Consumidor: o relatório não reescreve a regra, ele a consulta.
def relatorio_descontos(clientes: list["Cliente"]) -> Decimal:
return sum(desconto_para(c, c.total_compras) for c in clientes)
Valores derivados e a armadilha do dado duplicado
Guardar um valor que pode ser calculado é uma forma específica de duplicação, e a mais cara
de corrigir. O total do pedido, a idade do cliente e o status consolidado da entrega são
derivados; se forem persistidos sem política, alguém vai esquecer de atualizar um deles, e o
relatório vai contar uma história diferente do sistema. A regra prática é derivar sempre que
o cálculo for barato, e, quando o custo exigir persistência, documentar a fonte de verdade,
o gatilho de atualização e a tolerância de defasagem. Materialized view e projeção de
leitura são os instrumentos legítimos dessa persistência, porque carregam política explícita
de refresh e dono declarado.
O sinal clássico de que o dado derivado virou duplicação perigosa é a divergência entre o
valor armazenado e o recalculado no dia seguinte. Monitore essa comparação em produção com
um job de auditoria periódico; quando a divergência aparece sem motivo de negócio, a
política de atualização falhou e o item entra na lista de correção.
O perigo da abstração errada
A pressa em eliminar parecença produz abstrações que precisam ser abertas a cada caso novo,
com parâmetros booleanos e condicionais internos que reproduzem, dentro de uma função, a
variedade que existia entre as cópias. Esse é o caminho para a cirurgia de escopleta:
qualquer mudança de regra exige tocar na abstração compartilhada, que agora afeta todos os
consumidores, inclusive os que não deveriam ser impactados. A heurística da regra dos três
ajuda: permita que a terceira ocorrência revele o eixo real de variação antes de abstrair.
Outro sinal de alerta é a abstração cujo nome não descreve regra nenhuma do domínio, apenas
o fato de que dois códigos eram parecidos. Quando houver dúvida entre duplicar e abstrair,
prefira a duplicação temporária: unir depois é barato, separar uma abstração errada é caro.
Duplicação deliberada entre serviços
Em arquiteturas de microserviços, a aplicação cega do DRY entre serviços é um erro clássico
com consequência arquitetural. Bibliotecas compartilhadas de regras de negócio criam
acoplamento de versão: todos os serviços precisam atualizar a biblioteca em sincronia, e uma
mudança simples vira release coordenada de vários times. A alternativa aceita pelo mercado é
duplicar de forma deliberada: cada serviço possui a própria cópia do modelo de dados e das
regras de que precisa, evolui sozinho e se protege com testes de contrato. A consistência
entre cópias é eventual e é tratada como decisão de produto, e não como bug.
O que continua valendo a pena compartilhar são utilidades estáveis e de baixo churn, como
formatação de logs, propagação de correlation id e clientes de telemetria; regra de negócio
não entra nessa lista. O trade-off é explícito: gasta-se armazenamento e esforço de
sincronização para comprar autonomia de deploy. A cópia também funciona como proteção de
falha: se o serviço de catálogo cai, o serviço de frete segue operando com a tabela de
preços da própria base, e o incidente fica isolado. Autonomia de time e isolamento de falha
são os ganhos reais; o preço é a renúncia à garantia de uniformidade instantânea entre
serviços.
Erros comuns e boas práticas
- Aplicar DRY a código que apenas se parece: identifique primeiro o motivo de mudança comum; sem ele, a cópia é incidental e deve permanecer separada.
- Compartilhar biblioteca de regras entre serviços: cria acoplamento de versão e release sincronizada; prefira duplicação deliberada com testes de contrato.
- Armazenar valores derivados sem necessidade: total de pedido, idade e status agregado devem ser calculados a partir da fonte, ou ter política de atualização explícita.
- Abstrair no primeiro par de ocorrências: espere a terceira para confirmar o eixo de variação e evitar flags booleanas que escondem regras distintas.
- Esquecer a documentação como conhecimento: um runbook que repete manualmente o que o pipeline faz diverge; trate documentação gerada e executável como fonte única.
- Ignorar a camada de dados: esquemas espelhados entre bancos de contextos diferentes precisam de dono claro e política de sincronização documentada.
Sinais em produção
A tabela a seguir conecta sintomas operacionais às causas de duplicação mais frequentes e às
respectivas mitigações aplicáveis.
| Sintoma | Causa provável | Mitigação |
|---|---|---|
| Bug corrigido reaparece semanas depois | Regra existente em mais de um ponto | Mapear cópias e extrair fonte única |
| Cobrança divergente entre relatório e fatura | Regra calculada em dois serviços | Derivar do mesmo módulo ou do mesmo evento |
| Release coordenada entre vários serviços | Biblioteca compartilhada de regras | Duplicação deliberada e testes de contrato |
| Abstração com cinco parâmetros booleanos | Unificação falsa de casos distintos | Desfazer a abstração e restaurar cópias |
| Mudança simples toca dez arquivos | Tipos e esquemas reescritos à mão | Gerar tipos a partir do contrato da API |