DRY vs. WET: quando copiar código é aceitável
1. O Dilema Clássico: DRY vs. WET
No universo do desenvolvimento de software, poucos princípios geram debates tão acalorados quanto DRY (Don't Repeat Yourself) e sua contraparte irônica WET (Write Everything Twice ou We Enjoy Typing). O princípio DRY foi cunhado por Andrew Hunt e David Thomas no livro The Pragmatic Programmer (1999), onde estabelece que "cada pedaço de conhecimento deve ter uma representação única, não ambígua e autoritativa dentro de um sistema".
A ideia é nobre: evitar duplicação de lógica reduz bugs, facilita manutenção e centraliza mudanças. No entanto, o fanatismo por DRY pode gerar abstrações prematuras e acoplamento desnecessário. Código que parece idêntico hoje pode representar conceitos de negócio completamente diferentes amanhã. O resultado? Abstrações frágeis que quebram em cascata quando um dos contextos evolui.
WET, por outro lado, é frequentemente tratado como anti-padrão, mas representa uma abordagem pragmática: escrever código duplicado de forma consciente quando a abstração custa mais do que a repetição.
2. Os Custos Ocultos da Abstração Forçada
Forçar DRY onde não cabe introduz complexidade acidental. Considere este exemplo:
# Abstração forçada (DRY mal aplicado)
def calcular_valor(produto, regiao, cliente_tipo):
if regiao == "SP":
imposto = produto.base * 0.18
elif regiao == "RJ":
imposto = produto.base * 0.20
if cliente_tipo == "VIP":
desconto = produto.base * 0.10
elif cliente_tipo == "normal":
desconto = 0
return produto.base + imposto - desconto
Aqui, a função tenta servir dois contextos diferentes (cálculo para SP e RJ, com regras de desconto distintas). Quando a legislação de SP mudar, o código do RJ pode ser afetado. O paradoxo da reutilização surge: ninguém ousa modificar essa função porque ela "funciona para tudo", mas na verdade funciona mal para tudo.
Dependências indesejadas aparecem quando duas funcionalidades que parecem iguais evoluem em direções opostas. O custo de desacoplar uma abstração mal feita é frequentemente maior que o custo de ter mantido o código duplicado.
3. Quando Copiar Código é a Escolha Correta
Existem cenários onde WET é a abordagem superior:
Estabilização de sistemas legados: Refatorar código crítico que funciona perfeitamente há anos pode introduzir riscos desnecessários. Se o sistema legado processa pagamentos e está estável, copiar um trecho para um novo módulo pode ser mais seguro que modificar o original.
Prototipação e experimentação: Em fases de descoberta, velocidade importa mais que pureza arquitetural. Duplicar código permite testar hipóteses rapidamente sem o overhead de criar abstrações que podem ser descartadas.
Contextos com regras de negócio divergentes: Mesma lógica, significados diferentes:
# Cópia intencional - estados com regras fiscais independentes
def calcular_icms_sp(valor):
# SP: alíquota 18%, base dupla
base = valor * 2
return base * 0.18
def calcular_icms_rj(valor):
# RJ: alíquota 20%, base simples com dedução
base = valor * 1.5
deducao = 50 if valor > 1000 else 0
return (base - deducao) * 0.20
Aqui, copiar foi a decisão correta: as regras de SP e RJ mudam em momentos diferentes e por razões diferentes. Uma abstração comum criaria um monstro de parâmetros condicionais.
4. Os Limites do WET: Quando a Cópia se Torna um Problema
WET tem custos reais que precisam ser gerenciados:
Efeito cascata de correções: Um bug de validação de CPF copiado em 10 lugares precisa ser corrigido 10 vezes. Em situações de emergência, isso pode significar deploys atrasados ou correções esquecidas.
Inconsistência silenciosa: Cópias que divergem sutilmente ao longo do tempo criam bugs intermitentes. Um validador de email que aceita "+" em um módulo mas rejeita em outro gera frustração para usuários.
Onboarding catastrófico: Novos desenvolvedores enfrentam um labirinto de código duplicado sem padrões claros. A curva de aprendizado dobra quando cada módulo tem sua própria versão de uma funcionalidade comum.
5. Estratégias para Decidir: DRY ou WET?
A regra dos três: Só abstraia após a terceira ocorrência. A primeira cópia é aprendizado, a segunda é confirmação do padrão, a terceira justifica a abstração.
Análise de taxa de mudança: Se duas cópias mudam sempre juntas (mesmo pull request, mesmo deploy), una-as. Se mudam separadamente (diferentes sprints, diferentes times), mantenha-as separadas.
Testes como bússola: Código duplicado é aceitável se for coberto por testes independentes e claros:
# Teste para cada contexto duplicado
def test_calcular_icms_sp():
assert calcular_icms_sp(1000) == 360.0
def test_calcular_icms_rj():
assert calcular_icms_rj(1000) == 285.0
Testes independentes permitem que cada cópia evolua sem quebrar as outras.
6. Padrões de Compromisso: Abstrações Flexíveis e Cópias Seguras
Template Method: Quando a estrutura é igual, mas o comportamento varia:
class CalculadoraImposto:
def calcular(self, valor):
base = self._calcular_base(valor)
aliquota = self._obter_aliquota()
return base * aliquota
class CalculadoraSP(CalculadoraImposto):
def _calcular_base(self, valor):
return valor * 2
def _obter_aliquota(self):
return 0.18
class CalculadoraRJ(CalculadoraImposto):
def _calcular_base(self, valor):
return valor * 1.5
def _obter_aliquota(self):
return 0.20
Copy-paste com documentação explícita: Marcar cópias intencionais:
# CÓPIA INTENCIONAL - Regra SP (29/10/2023)
# Se modificar, verifique se RJ precisa da mesma alteração
def validar_cnpj_sp(documento):
# ... lógica específica SP
Módulos de utilidade de domínio: Isole trechos genuinamente reutilizáveis (formatação de datas, validações universais) sem forçar acoplamento entre contextos de negócio.
7. Ferramentas e Práticas para Gerenciar a Tensão
Linters com tolerância configurável: SonarQube, PMD e jscpd permitem definir limites de duplicação aceitáveis. Configure thresholds diferentes para módulos core (DRY estrito) vs. módulos experimentais (WET tolerado).
Code reviews focados em intenção: Durante revisões, pergunte: "Essas cópias vão divergir no futuro?" Se a resposta for sim, aceite a duplicação. Se for não, exija abstração.
Refatoração incremental: Transforme WET em DRY apenas quando o custo da duplicação supera o custo da abstração. Monitore métricas como "tempo para corrigir bug" e "número de arquivos modificados por feature" para tomar decisões baseadas em dados.
O equilíbrio entre DRY e WET não é técnico — é contextual. Código não existe no vácuo; ele serve a domínios de negócio, prazos e equipes com dinâmicas próprias. A sabedoria está em saber quando a abstração une e quando ela amarra. Copiar código não é pecado quando feito com consciência, documentação e testes. O pecado é copiar sem pensar.
Referências
- The Pragmatic Programmer: Your Journey to Mastery (20th Anniversary Edition) — Livro seminal que originou o princípio DRY, com discussões atualizadas sobre pragmatismo no desenvolvimento.
- Rule of Three (Refactoring Guru) — Artigo detalhado sobre a regra dos três e quando aplicar refatoração para evitar abstrações prematuras.
- DRY vs WET Code: When to Use Which (freeCodeCamp) — Tutorial prático com exemplos comparativos entre abordagens DRY e WET em diferentes cenários.
- SonarQube Documentation: Duplications — Documentação oficial sobre como configurar detecção de duplicação de código no SonarQube, incluindo limites de tolerância.
- jscpd: Copy/Paste Detector for JavaScript/TypeScript — Ferramenta open-source para detectar duplicação de código com suporte a múltiplas linguagens e configuração de thresholds.
- When DRY is a Lie: The Hidden Costs of Over-Abstraction (DEV Community) — Artigo técnico discutindo casos reais onde o fanatismo por DRY gerou mais problemas que soluções.
- Refactoring: Improving the Design of Existing Code (Martin Fowler) — Clássico que aborda o equilíbrio entre duplicação e abstração, com catálogo de refatorações para gerenciar código WET.