Erros Comuns em Design Patterns: O que Evitar para Escrever Código Melhor

Erros Comuns em Design Patterns: O que Evitar para Escrever Código Melhor

“`html





Erros comuns em Design Patterns que você deve evitar


⚙️ Design Patterns • checklist técnico

Erros comuns em Design Patterns que você deve evitar

Design Patterns são ferramentas para resolver problemas recorrentes de software. O problema é quando você aplica o padrão
cedo demais, de forma forçada, ou sem respeitar os trade-offs. Abaixo, eu listo os erros mais comuns (e caros) para você
evitar na prática.

✅ foco em manutenibilidade
✅ foco em arquitetura
✅ foco em qualidade de código

1) Aplicar “por moda” em vez de resolver um problema real

O erro mais frequente é escolher um padrão antes de entender o problema. Você termina com uma estrutura bonita,
mas que não melhora acoplamento, testabilidade ou evolução do sistema.

  • Macro-sintoma: você cria classes e interfaces “só para encaixar” um padrão.
  • Micro-sintoma: métodos viram pass-through (apenas encaminham), sem ganho real.
  • Resultado: complexidade cresce, e a regra de negócio continua difícil de alterar.
Regra prática: identifique primeiro o motivo de mudança (ex.: adicionar variações, alternar algoritmos, compor responsabilidades).
Só então selecione o padrão que atende esse motivo.

2) “Forçar” o padrão e ignorar os trade-offs

Todo padrão vem com custo: mais tipos, mais indireção, mais pontos de falha e mais código para navegar.
Se você não mede o custo contra o benefício, vira arquitetura decorativa.

Exemplo típico: usar Strategy quando não há variação significativa ao longo do tempo.
Se o algoritmo “quase nunca muda”, a abstração pode virar ruído.
  • Indireção excessiva: loops de delegação dificultam debugging e aumentam latência mental.
  • Abstrações prematuras: interfaces sem contrato claro e sem responsabilidade bem definida.
  • Convergência de responsabilidades: “um padrão resolve tudo” — e nada fica realmente coeso.

Quando a decisão é correta, você sente: menos acoplamento, mais clareza e mudanças locais.
Quando está errada, você sente o contrário — o padrão “esconde” o problema.

3) Quebrar o contrato do padrão (implementação incompleta ou distorcida)

Alguns erros aparecem quando a estrutura do padrão até existe, mas o comportamento viola o contrato esperado.
Isso gera bugs sutis, inconsistência e manutenção cara.

  • Observer “meio”: notifica, mas não respeita ordem/consistência, ou permite que observadores mutem estado indevidamente.
  • Factory sem intenção: cria objetos, mas repete lógica de decisão no chamador (ou seja, a factory não encapsula de verdade).
  • Singleton “na marra”: implementação não controlada por concorrência, ou com dependências globais escondidas.
  • Template Method: subclasses alteram invariantes do algoritmo base, destruindo o objetivo de padronização.
Alerta: se a implementação permite estados inválidos ou remove garantias do padrão,
você não está usando o padrão — está copiando a forma.

4) Transformar padrões em “fábrica de complexidade” (código difícil de testar e evoluir)

O padrão deveria facilitar testes e evolução. O erro é quando a arquitetura fica “engessada” por excesso de abstrações,
ou quando dependências ficam difíceis de substituir.

  • Dificuldade de teste: acoplamento a concretos e efeitos colaterais escondidos em camadas intermediárias.
  • Acoplamento temporal: ordem de inicialização/uso vira requisito implícito (bug de runtime inevitável).
  • Configuração frágil: seleção de estratégia por if/else espalhados em vários lugares.
  • Logging e métricas inconsistentes: a delegação dificulta rastrear o fluxo real da requisição.

Uma forma de identificar esse problema é observar: “onde eu altero para adicionar uma nova variação?”
Se a resposta for “em muitos arquivos e com muito if”, a escolha arquitetural provavelmente está errada.

Exemplo prático: evitando Strategy “forçado” com uma decisão central e contratos claros

A ideia não é “decorar com Strategy”, e sim garantir que a escolha do comportamento seja local, previsível e testável.
Abaixo, eu mostro um jeito de centralizar a seleção do algoritmo, mantendo um contrato consistente entre as variações.

// Contrato claro: todo algoritmo deve produzir o mesmo tipo de saída e respeitar invariantes.
export interface PaymentCalculator {
  calculate(amountCents: number): number; // retorna valor final em centavos
}

// Implementações focadas: cada classe tem uma única razão para mudar.
export class CreditCardCalculator implements PaymentCalculator {
  calculate(amountCents: number): number {
    const fee = Math.round(amountCents * 0.029);
    return amountCents + fee;
  }
}

export class PixCalculator implements PaymentCalculator {
  calculate(amountCents: number): number {
    const discount = Math.round(amountCents * 0.01);
    return amountCents - discount;
  }
}

// Seleção centralizada: evita if/else espalhados e melhora manutenção.
export class PaymentCalculatorFactory {
  static create(method: "credit_card" | "pix"): PaymentCalculator {
    switch (method) {
      case "credit_card":
        return new CreditCardCalculator();
      case "pix":
        return new PixCalculator();
      default:
        // Em projetos reais, prefira um erro explícito e cedo.
        throw new Error(`Método de pagamento não suportado: ${method}`);
    }
  }
}

// Uso: quem chama não conhece detalhes internos.
export function calculatePayment(
  method: "credit_card" | "pix",
  amountCents: number
): number {
  const calculator = PaymentCalculatorFactory.create(method);
  return calculator.calculate(amountCents);
}
O que isso evita: decisão espalhada no código, contratos implícitos e acoplamento com classes concretas em camadas indevidas.

Quer melhorar ainda mais seu código?

Se você curte aprender padrões com aplicação prática, eu recomendo continuar pelos outros posts do
yurideveloper.com.br — foque em arquitetura, testes e decisões que deixam o sistema fácil de evoluir.

© yurideveloper.com.br • Aprenda padrões para resolver problemas — não para adicionar complexidade sem necessidade.




“`