Design de software

Entenda de verdade o princípio DRY: uma abstração errada é mais perigosa que a duplicação de código

Ao fazer uma revisão de código, há uma cena que encontro com frequência especial.

4 min de leitura
Imagem de capa de Entenda de verdade o princípio DRY: uma abstração errada é mais perigosa que a duplicação de código

Ao fazer uma revisão de código, há uma cena que encontro com frequência especial.

Você pesquisa para corrigir uma funcionalidade e encontra um código quase idêntico em três lugares. Corrige um, e os outros dois continuam como bugs.

A protagonista de hoje é DRY, o princípio que ataca esse problema diretamente. Ele anda lado a lado com o princípio KISS, que vimos da última vez.

DRY é a sigla de “Don’t Repeat Yourself”. É o princípio de não repetir o mesmo conhecimento dentro de um sistema.

Andy Hunt e Dave Thomas apresentaram esse conceito no livro de 1999 <The Pragmatic Programmer>, e a definição original é bastante precisa.

Todo conhecimento deve ter uma única representação inequívoca e autorizada dentro de um sistema.

A palavra importante aqui não é “código”, mas “conhecimento”. Esse é o ponto central da conversa de hoje.


Por que copiar e colar código é um problema?

Copiar e colar não é errado por si só. O problema acontece depois.

Se a mesma lógica existe em três lugares, quando os requisitos mudam você precisa encontrar e corrigir os três. Se deixar um passar, ele vira um bug.

// O cálculo do frete está espalhado por três arquivos
// Cart.swift
let fee = total >= 50000 ? 0 : 3000

// Checkout.swift
let shippingFee = totalPrice >= 50000 ? 0 : 3000

// OrderSummary.swift
let delivery = price >= 50000 ? 0 : 3000

E se um dia chegar o requisito de “reduzir o limite do frete grátis para 30 mil wones”? Você precisa encontrar os três arquivos. Como os termos de busca também são diferentes, algum deles certamente ficará para trás.

// Concentrar o conhecimento em um só lugar
// Shipping.swift
enum ShippingPolicy {
    static let freeShippingThreshold = 50000
    static let fee = 3000

    static func shippingFee(for total: Int) -> Int {
        total >= freeShippingThreshold ? 0 : fee
    }
}

Agora, quando a política mudar, basta corrigir um único lugar. Isso é DRY.


A duplicação é de “conhecimento”, não de código

Mas é aqui que muita gente escorrega. Elas interpretam DRY como “junte todo código que parece semelhante”.

A duplicação mencionada pelo DRY não é a forma do código, mas o conhecimento — ou seja, a duplicação das regras de negócio.

Essa distinção é importante porque existe código que só se parece por acaso.

Situação É duplicação?
Lógica de cálculo do frete em três lugares Duplicação real (mesmo conhecimento)
A validação do cadastro e a validação da inscrição em um evento são parecidas por acaso Duplicação falsa (conhecimento diferente)
A constante 3000 aparece separadamente no frete e no limite para acumular pontos Duplicação falsa (significados diferentes)

A validação do cadastro e a validação da inscrição em eventos parecem iguais porque atualmente ambas exigem “nome obrigatório e telefone com 11 dígitos”, mas as duas regras mudam por motivos diferentes. Se você as unir, uma mudança nas regras do evento pode quebrar o cadastro.

Nem tudo que parece semelhante representa o mesmo conhecimento
Nem tudo que parece semelhante representa o mesmo conhecimento

Uma abstração precipitada custa mais que a duplicação

Por isso, nas comunidades de desenvolvimento, é comum ouvir: “prefer duplication over the wrong abstraction”. A frase é de Sandy Metz, uma conhecida desenvolvedora Ruby.

Quando você força a união de dois trechos de código que apenas parecem semelhantes, acontece o seguinte.

  1. Criar uma função comum
  2. O requisito de um lado muda, então você adiciona um parâmetro de opção
  3. O outro lado também muda, então você adiciona uma ramificação if
  4. De repente, vira uma função com cinco parâmetros que ninguém entende

Nesse ponto, teria sido melhor manter duas cópias do código.

As quatro etapas do colapso de uma função unida à força
As quatro etapas do colapso de uma função unida à força

Por isso, no trabalho, muita gente usa a Regra de Três. Quando um padrão aparece duas vezes, apenas observe; quando aparecer pela terceira vez, faça a abstração. Depois de cerca de três repetições, fica claro se é realmente o mesmo conhecimento ou apenas coincidência.


Meu critério de decisão

Quando encontro uma duplicação, faço uma pergunta.

Esses dois códigos mudam pelo mesmo motivo?

Se mudam pelo mesmo motivo, é duplicação real, então eu uno os dois. Se mudam por motivos diferentes, deixo como estão, por mais parecidos que sejam.

Antes de unir, basta fazer esta pergunta
Antes de unir, basta fazer esta pergunta

Conclusão de hoje

Em resumo, o princípio DRY se resume a isto.

  • A unidade da duplicação é o conhecimento (as regras de negócio), não a aparência do código.
  • Una apenas o código que muda pelo mesmo motivo. Deixe como está o código que só parece semelhante por acaso.
  • Se não tiver certeza, use a Regra de Três. Não é tarde demais para abstrair na terceira repetição.

Se KISS significa “mantenha simples”, DRY significa “mantenha o conhecimento em um só lugar”. No fim, ambos falam sobre reduzir o custo de mudança.

Você provavelmente consegue lembrar de uma lógica que aparece em três lugares sempre que a pesquisa é feita. Essa é a candidata à refatoração de hoje.