Princípios YAGNI e DRY: quando aplicar

1. Introdução aos princípios YAGNI e DRY

No desenvolvimento de software, dois princípios fundamentais frequentemente entram em conflito: YAGNI (You Aren't Gonna Need It) e DRY (Don't Repeat Yourself). O YAGNI, originado no Extreme Programming (XP) por Kent Beck, prega que você não deve adicionar funcionalidades até que sejam realmente necessárias. Já o DRY, cunhado por Andy Hunt e Dave Thomas em "The Pragmatic Programmer", defende que cada conhecimento deve ter uma representação única, inequívoca e autoritária dentro de um sistema.

Compreender quando aplicar cada princípio é essencial para evitar tanto o excesso de abstração quanto a duplicação desnecessária de código. Este artigo explora o equilíbrio entre YAGNI e DRY no contexto de Temas — Lista Final (1200 temas), oferecendo exemplos práticos e diretrizes para decisões conscientes.

2. YAGNI: evitando funcionalidades desnecessárias

YAGNI é particularmente valioso em projetos com requisitos voláteis e ciclos de desenvolvimento curtos. A armadilha mais comum é a superengenharia: criar abstrações complexas "por precaução", antecipando necessidades futuras que podem nunca se materializar.

Exemplo de violação de YAGNI:

# Código que antecipa múltiplos tipos de desconto em um sistema simples
class CalculadoraDesconto:
    def __init__(self):
        self.estrategias = {
            'percentual': self.desconto_percentual,
            'fixo': self.desconto_fixo,
            'cumulativo': self.desconto_cumulativo,
            'sazonal': self.desconto_sazonal  # Ainda não requisitado
        }

    def calcular(self, tipo, valor, parametro):
        return self.estrategias.get(tipo, lambda: 0)(valor, parametro)

    def desconto_percentual(self, valor, percentual):
        return valor * (1 - percentual/100)

    def desconto_fixo(self, valor, fixo):
        return valor - fixo

    def desconto_cumulativo(self, valor, niveis):
        # Complexidade desnecessária se apenas descontos simples são requisitados
        pass

    def desconto_sazonal(self, valor, temporada):
        # Funcionalidade especulativa
        pass

Estratégias para aplicar YAGNI:
- Implemente apenas o que o requisito atual exige
- Resista à tentação de criar "hooks" genéricos para funcionalidades futuras
- Prefira soluções simples e específicas até que a necessidade real surja

3. DRY: eliminando duplicação de código

A duplicação de código pode assumir várias formas: código literal idêntico, lógica de negócio repetida, documentação inconsistente e configuração espalhada. Aplicar DRY reduz erros e facilita a manutenção.

Exemplo de violação de DRY:

# Duplicação de lógica de validação de e-mail em múltiplos lugares
def validar_usuario(email):
    if '@' not in email or '.' not in email.split('@')[-1]:
        raise ValueError("Email inválido")
    # ... resto da lógica

def validar_pedido(email_cliente):
    if '@' not in email_cliente or '.' not in email_cliente.split('@')[-1]:
        raise ValueError("Email inválido")
    # ... resto da lógica

Solução DRY:

def validar_email(email):
    if '@' not in email or '.' not in email.split('@')[-1]:
        raise ValueError("Email inválido")
    return True

def validar_usuario(email):
    validar_email(email)
    # ... resto da lógica

def validar_pedido(email_cliente):
    validar_email(email_cliente)
    # ... resto da lógica

Cuidados importantes:
- Duplicação acidental: código que parece igual mas serve a propósitos diferentes
- Duplicação essencial: repetição necessária por questões de performance ou acoplamento

4. Conflitos entre YAGNI e DRY na prática

O conflito clássico surge quando decidimos abstrair cedo demais (violando YAGNI) ou repetir código (violando DRY). A Regra dos Três (Rule of Three) oferece um ponto de equilíbrio: só abstraia quando o código se repetir três vezes.

Análise de custo-benefício:

# Primeira ocorrência: implementação direta (YAGNI)
def calcular_frete_simples(peso):
    return peso * 5.0

# Segunda ocorrência: ainda aceitável repetir (YAGNI)
def calcular_frete_expresso(peso):
    return peso * 5.0 + 10.0

# Terceira ocorrência: momento de abstrair (DRY)
def calcular_frete(peso, tipo='simples'):
    base = peso * 5.0
    return base + (10.0 if tipo == 'expresso' else 0.0)

5. Exemplos práticos de aplicação combinada

Estudo de caso: sistema de e-commerce com regras de desconto

Cenário inicial (YAGNI aplicado):

class Pedido:
    def __init__(self, itens, cliente):
        self.itens = itens
        self.cliente = cliente

    def calcular_total(self):
        total = sum(item.preco * item.quantidade for item in self.itens)
        # Desconto simples para clientes VIP
        if self.cliente.tipo == 'vip':
            total *= 0.9
        return total

Quando surgem novas regras (DRY aplicado):

class CalculadoraDesconto:
    @staticmethod
    def aplicar(regra, total):
        if regra == 'vip':
            return total * 0.9
        elif regra == 'primeira_compra':
            return total * 0.85
        elif regra == 'fidelidade':
            return total * 0.95
        return total

class Pedido:
    def __init__(self, itens, cliente):
        self.itens = itens
        self.cliente = cliente

    def calcular_total(self):
        total = sum(item.preco * item.quantidade for item in self.itens)
        return CalculadoraDesconto.aplicar(self.cliente.regra_desconto, total)

Refatoração segura em código legado:

# Código legado com duplicação
def processar_pagamento_cartao(valor, parcelas):
    taxa = 0.03 if parcelas <= 6 else 0.05
    return valor * (1 + taxa)

def processar_pagamento_boleto(valor):
    taxa = 0.02
    return valor * (1 + taxa)

# Refatoração aplicando DRY sem violar YAGNI
def calcular_taxa_pagamento(tipo, valor, parcelas=1):
    taxas = {
        'cartao': 0.03 if parcelas <= 6 else 0.05,
        'boleto': 0.02
    }
    return valor * (1 + taxas.get(tipo, 0))

6. Ferramentas e técnicas para manter o equilíbrio

Para aplicar YAGNI e DRY de forma consistente, utilize:

  • Análise estática: ferramentas como SonarQube detectam duplicação de código (DRY) e complexidade excessiva (possível violação de YAGNI)
  • Revisão de código: Checklists focados em código morto e abstrações prematuras
  • Métricas: Acompanhe acoplamento e coesão para avaliar se abstrações são justificadas
  • Testes: Testes unitários ajudam a identificar quando a duplicação se torna problemática

7. Conclusão e boas práticas recomendadas

O equilíbrio entre YAGNI e DRY depende do contexto: tamanho da equipe, prazo, maturidade do projeto e frequência de mudanças esperadas.

Checklist para decisões:

  1. A funcionalidade é requisitada agora? (Se não, YAGNI manda não implementar)
  2. O código se repete três vezes ou mais? (Se sim, considere DRY)
  3. A abstração proposta reduzirá a complexidade? (Se não, evite)
  4. A equipe entende a abstração? (Se não, prefira simplicidade)

Integrar YAGNI e DRY como filosofia de desenvolvimento ágil significa reconhecer que software é um artefato vivo, que deve evoluir organicamente. Comece simples, repita quando necessário e abstraia apenas quando o custo da duplicação superar o custo da abstração.

Referências