DRY e os perigos da abstração prematura
DRY não é sobre eliminar linhas iguais, e sim uma fonte de verdade por conhecimento. Entenda a regra de três e por que abstrair cedo custa caro.
Neste artigo
Poucos princípios de engenharia de software são citados com tanta frequência e compreendidos com tão pouca profundidade quanto o DRY, sigla de "Don't Repeat Yourself", ou "não se repita". A leitura superficial que a maioria faz dele é quase reflexiva: viu duas linhas iguais, junta; viu um trecho que se parece com outro, extrai uma função. Essa interpretação mecânica, aplicada sem critério, é responsável por uma parcela enorme das abstrações ruins que assombram bases de código maduras, aquelas funções genéricas cheias de parâmetros de configuração que ninguém entende e que ninguém ousa mexer.
O problema é que DRY nunca foi sobre eliminar caracteres repetidos na tela. Ele foi formulado para tratar de conhecimento, não de texto. A repetição que o princípio combate é a duplicação de uma decisão, de uma regra, de um fato sobre o domínio, não a coincidência visual de dois trechos que, por acaso, se parecem hoje mas respondem a razões diferentes. Confundir esses dois tipos de repetição é o erro que transforma um bom princípio em uma fábrica de acoplamento.
Este artigo pretende recuperar o sentido original de DRY e, no mesmo movimento, defender que abstrair cedo demais costuma ser mais caro do que conviver com um pouco de duplicação. Vamos distinguir duplicação acidental de essencial, apresentar a regra de três como antídoto contra a ansiedade de generalizar, examinar o custo do acoplamento que uma abstração precoce impõe, e discutir quando duplicar é, na verdade, a decisão mais sábia.
O que DRY realmente significa#
A formulação clássica do princípio diz que cada pedaço de conhecimento deve ter uma representação única, não ambígua e autoritativa dentro de um sistema. A palavra decisiva ali é "conhecimento". DRY fala de ideias, regras e fatos, não de linhas de código. Duas passagens do programa violam DRY quando codificam a mesma decisão, de modo que, se essa decisão mudar, ambas precisam mudar juntas, sob pena de o sistema entrar em contradição consigo mesmo.
Pense na alíquota de um imposto, no formato de um identificador válido, na regra que define quando um pedido pode ser cancelado. Cada uma dessas coisas é um pedaço de conhecimento sobre o domínio. Se essa regra estiver espalhada em cinco lugares diferentes do código, uma mudança de negócio exigirá que alguém encontre e altere os cinco, e o esquecimento de um único ponto produzirá um bug silencioso, em que parte do sistema opera sob a regra nova e parte sob a antiga. É contra esse tipo de fragmentação da verdade que DRY existe.
Repare que essa definição não menciona semelhança visual em nenhum momento. Dois trechos podem ser textualmente idênticos e ainda assim representar conhecimentos completamente distintos, que apenas coincidem no presente. Inversamente, a mesma regra de negócio pode aparecer escrita de formas bem diferentes em pontos distintos do código e, mesmo sem se parecerem, violar DRY, porque duplicam a decisão. O critério é o conhecimento, não a aparência, e essa é a distinção que a leitura ingênua do princípio perde.
Duplicação acidental versus essencial#
A partir dessa definição, torna-se possível separar dois tipos de repetição que a intuição costuma misturar. A duplicação essencial é aquela em que dois trechos existem porque expressam a mesma regra, o mesmo conhecimento. Essa é a duplicação que DRY manda eliminar, porque a existência de duas cópias da mesma verdade é um convite ao descompasso. Unificá-las em uma fonte única é o serviço que o princípio presta.
A duplicação acidental é outra história. Nela, dois trechos se parecem hoje por coincidência, mas respondem a motivações independentes que podem, e frequentemente vão, divergir no futuro. Duas telas que exibem uma lista de forma semelhante, dois cálculos que por ora usam a mesma fórmula, duas validações que atualmente checam o mesmo formato: a semelhança é real, mas é superficial. Não há um único pedaço de conhecimento por trás delas; há dois, que só por acaso convergiram neste instante.
Quando você funde uma duplicação acidental em uma única abstração, você amarra dois destinos que deveriam ser livres. No dia em que uma das telas precisar exibir a lista de outro jeito, ou um dos cálculos precisar de um ajuste que o outro não quer, você descobre que a abstração que criou para "eliminar repetição" agora atrapalha, porque qualquer mudança em um lado afeta o outro. Aí começa o desfile de parâmetros booleanos, condicionais internas e casos especiais que transformam a função unificada num monstro que serve mal a ambos os usos.
A pergunta que separa os dois casos é esta: se essa regra mudar, os dois lugares devem mudar juntos, sempre, necessariamente? Se a resposta for um sim inequívoco, a duplicação é essencial e merece ser unificada. Se a resposta for "depende" ou "provavelmente não", você está diante de uma coincidência, e forçar a unificação é criar um problema onde não havia.
A regra de três#
Diante da dificuldade de distinguir, no calor da escrita, o que é coincidência do que é conhecimento compartilhado, a comunidade de software consagrou uma heurística prática e valiosa: a regra de três. Ela diz, de forma deliberadamente simples, que você deve tolerar a duplicação até a terceira ocorrência antes de extrair uma abstração.
A lógica por trás dessa contagem é estatística e psicológica ao mesmo tempo. Com apenas uma ocorrência, não há duplicação alguma. Com duas, você tem pouca informação sobre a natureza da semelhança: pode ser uma regra genuína ou uma coincidência, e é cedo demais para saber qual. É na terceira aparição que o padrão começa a se revelar de fato, porque agora você tem três exemplos concretos para observar o que varia entre eles e o que permanece constante. Essa variação observada é o que informa qual deve ser o eixo da abstração.
A regra de três combate um viés muito específico: a ansiedade de generalizar cedo, que confunde disciplina com pressa. Diante da segunda ocorrência, muita gente sente um desconforto quase físico com a repetição e corre para abstrair, projetando uma generalização baseada em dois pontos apenas, que é pouco para inferir a forma certa. Abstrações construídas sobre dois exemplos costumam errar o eixo: adivinham errado o que vai variar e o que vai ficar fixo, e depois precisam ser torturadas para acomodar a realidade.
Esperar a terceira ocorrência não é preguiça nem tolerância à bagunça. É a decisão consciente de coletar evidência suficiente antes de tomar uma decisão de design que será cara de reverter. A duplicação temporária é barata e local; a abstração errada é cara e espalhada. Trocar uma dose pequena de repetição por informação sobre a forma correta da abstração costuma ser um ótimo negócio.
O custo do acoplamento por abstrair cedo#
O que a leitura ingênua de DRY ignora é que abstração não é grátis. Toda vez que você extrai um trecho comum e faz vários pontos do código chamarem essa peça única, você cria acoplamento: esses pontos agora dependem da abstração e, por meio dela, uns dos outros. Antes eram independentes; agora estão ligados por um destino comum. Essa dependência é o preço que se paga pela unificação, e ela nem sempre vale o que custa.
Enquanto a abstração representa uma verdade genuína e estável, o acoplamento é benéfico, porque garante que a regra permaneça consistente em todos os pontos. Mas quando a abstração foi construída sobre uma coincidência, o acoplamento vira uma corrente. Cada consumidor que precisa de um comportamento ligeiramente diferente força a adição de um parâmetro, de uma flag, de um ramo condicional dentro da abstração. A peça que nasceu para simplificar vai acumulando complexidade até se tornar mais difícil de entender do que a duplicação que ela substituiu.
Há um ditado bastante repetido entre engenheiros experientes que resume o dilema: uma pouca duplicação é mais barata do que a abstração errada. A duplicação tem um custo visível e localizado, você sabe exatamente onde estão as cópias e pode alterá-las de forma independente. A abstração errada tem um custo oculto e distribuído, que se manifesta como acoplamento indesejado, como medo de mexer, como parâmetros que ninguém entende e como bugs que aparecem em um consumidor quando alguém alterou a peça pensando em outro.
Pior ainda é o fenômeno da abstração que vaza. Uma abstração é uma promessa de esconder detalhes por trás de uma interface simples. Quando ela foi mal construída, esses detalhes vazam: o consumidor precisa saber como a peça funciona por dentro para usá-la corretamente, precisa passar exatamente a combinação certa de flags, precisa entender qual ramo interno seu caso aciona. A abstração que vaza é a pior de todos os mundos, porque impõe o custo do acoplamento sem entregar o benefício da simplicidade, você ganhou a dependência e perdeu o encapsulamento.
WET, AHA e a reabilitação de duplicar#
Como reação ao dogmatismo do DRY mal entendido, surgiram contrapontos deliberadamente provocadores. Um deles é o WET, "Write Everything Twice" ou "escreva tudo duas vezes", uma brincadeira com a sigla oposta que reafirma, na prática, a regra de três: só considere abstrair quando a mesma coisa aparecer pelo menos duas vezes de fato, e não ao primeiro sinal de semelhança.
Mais influente e mais sério é o princípio AHA, "Avoid Hasty Abstractions", ou "evite abstrações apressadas". A tese do AHA é que o pecado mais caro não é a duplicação, e sim a abstração prematura, e que o desenvolvedor deve otimizar para a facilidade de mudar de ideia, não para a ausência de repetição. Como as abstrações erradas são muito mais difíceis de desfazer do que a duplicação é de unificar, faz sentido pecar pelo lado da paciência: espere entender o padrão antes de cristalizá-lo em código.
Esses contrapontos não pregam a bagunça nem revogam o valor de DRY. Eles apenas recolocam o princípio no seu devido lugar, como uma ferramenta a serviço da manutenibilidade e não como um fim em si. O objetivo nunca foi um código com zero linhas repetidas; foi um código fácil de mudar. Quando a busca cega por zero repetição produz um código difícil de mudar, o princípio foi traído por sua própria aplicação, e é isso que AHA e WET vêm lembrar.
Quando duplicar é a escolha certa#
Reconhecer situações em que a duplicação é a melhor decisão é sinal de maturidade, não de relaxamento. Há vários contextos em que manter duas cópias independentes é objetivamente superior a unificá-las.
- Contextos diferentes que evoluem separados. Código que atende a domínios distintos, ainda que hoje pareça idêntico, tende a divergir conforme cada domínio evolui sob suas próprias pressões. Manter as cópias separadas permite que cada uma siga seu caminho sem negociar com a outra a cada mudança.
- Fronteiras entre módulos ou serviços. Duplicar uma pequena estrutura de dados ou uma validação simples de cada lado de uma fronteira costuma ser preferível a criar uma dependência compartilhada que acopla dois componentes que deveriam ser independentes. O custo da cópia é menor que o custo do acoplamento entre fronteiras.
- Semelhança superficial e instável. Quando dois trechos se parecem mas você suspeita que vão divergir, duplicar preserva a liberdade. É mais fácil unificar mais tarde, quando o padrão estiver claro, do que separar depois uma abstração que grudou dois casos que não queriam ficar juntos.
- Legibilidade local. Às vezes, uma pequena repetição deixa cada trecho autoexplicativo, enquanto a abstração obrigaria o leitor a saltar para outro arquivo para entender o que está acontecendo ali. A duplicação, nesses casos, paga por si em clareza.
O fio condutor é sempre o mesmo: a decisão de duplicar ou abstrair deve maximizar a facilidade de mudar o sistema no futuro, não minimizar a contagem de linhas no presente. Duplicar por preguiça de pensar é um erro; duplicar por decisão consciente de preservar independência é engenharia.
O equilíbrio como habilidade#
No fim, DRY bem aplicado é uma questão de equilíbrio, e equilíbrio é uma habilidade que se desenvolve com experiência e cicatrizes. O desenvolvedor iniciante tende a um de dois extremos: ou ignora completamente a repetição e produz código que se contradiz quando uma regra muda, ou persegue a eliminação de toda repetição e produz abstrações precoces que engessam o sistema. Os dois extremos custam caro, e a maestria está no meio, no julgamento caso a caso.
Esse julgamento se apoia em algumas perguntas que vale a pena internalizar. Esses trechos representam o mesmo conhecimento ou apenas se parecem? Se essa regra mudar, eles têm de mudar juntos, obrigatoriamente? Já vi esse padrão três vezes ou estou generalizando com base em duas? A abstração que estou prestes a criar vai simplificar de verdade ou vai só transferir a complexidade para uma peça cheia de parâmetros? Responder a essas perguntas com honestidade, antes de extrair a função, evita a maioria das abstrações ruins.
O princípio DRY continua valioso quando entendido em seus próprios termos: uma fonte única de verdade para cada pedaço de conhecimento do sistema. O que precisa morrer é a caricatura dele, a caça obsessiva a linhas parecidas, que confunde texto com conhecimento e acoplamento com organização. Entre a duplicação descuidada e a abstração apressada existe um caminho estreito, e percorrê-lo com discernimento é uma das marcas mais confiáveis de um engenheiro que amadureceu.