Portfólio Blog Sobre
Anti-Patterns: evite problemas conhecidos

Anti-Patterns: evite problemas conhecidos

Acredito que ninguém sabe ou conhece tudo. Somos seres humanos e isso é uma característica normal. Em épocas passadas, todos nós já escrevemos códigos ruins. Porém, uma coisa é certa: é possível evitar aquilo que já sabemos que dá problema.

Anti-patterns são exatamente isso. São padrões ruins, soluções que parecem razoáveis no momento, mas que criam dificuldades reais de manutenção, legibilidade e confiabilidade ao longo do tempo. Conhecê-los é uma vantagem real no dia a dia.

Anti-padrão: código espaguete

Esse é o clássico. Sem estrutura, sem contrato, sem intenção aparente. O código até funciona, mas qualquer mudança vira uma operação de risco.

❌ Deixa eu ver esse espaguetinho…
// Exemplo propositalmente RUIM
// Código todo misturado, sem conceitos claros

realizaVenda(123);

function realizaVenda(x) {
  let resultado;

  // nomes ruins
  let p = buscaPedido(x);

  if (p != null) {
    if (p.itens && p.itens.length > 0) {
      if (!p.c.inadimplente) {
        // começa a fazer um monte de coisa no meio
        if (p.total > 100) {
          p.desconto = 10;
        } else {
          p.desconto = 0;
        }

        apply(p);

        function apply(p) {
          if (p.desconto) {
            p.total = p.total - p.desconto;
          }
        }

        let salvo = salvaPedido(p);

        if (salvo) {
          resultado = salvo;

          // lógica aleatória no meio
          if (Math.random() > 0.5) {
            console.log("Log qualquer");
          } else {
            console.warn("Outro log");
          }
        } else {
          resultado = null;
        }
      } else {
        // mais lógica aninhada com função no meio
        notify(detalhesDoPedido);
        resultado = false;

        // nomes ruins e mistura de português com inglês
        function notify(p) {
          console.log("cliente inadimplente", p?.cliente?.nome);
          return true;
        }
      }
    } else {
      resultado = undefined;
    }
  } else {
    resultado = null;
  }

  // código morto
  if (false) {
    console.log("nunca executa");
  }

  return resultado;

  function salva(p) {
    if (!p) return;
    if (p.total < 0) return null;
    return { ...p, salvo: true };
  }
}

Sem condições de ver essa tortura. Cada if esconde mais um nível de complexidade. A função salva no rodapé nem é chamada em lugar nenhum. E o Math.random() no meio de uma regra de negócio… melhor nem comentar. 😅

Por isso é bom evitar ao máximo esse tipo de código. Hoje, com o conhecimento disponível, é possível fazer muito melhor.

Os principais pontos do anti-padrão

Resumindo o que torna esse código problemático:

Nomes e legibilidade

  • Identificadores sem significado (x, p, c, apply)
  • Mistura de idiomas no mesmo arquivo (português + inglês)
  • Sem intenção explícita nos identificadores

Controle de fluxo

  • Aninhamento excessivo (if dentro de if dentro de if)
  • Falta de early return
  • Lógica condicional espalhada e difícil de seguir
  • Uso de != com coerção implícita

Retorno e contrato

  • Múltiplos tipos de retorno (null, undefined, false, objeto)
  • Falta de contrato claro de retorno
  • Fluxo baseado em verificar nulo a cada passo

Estrutura e design

  • Função com múltiplas responsabilidades
  • Regra de negócio misturada com persistência e log
  • Funções declaradas dentro de outras sem necessidade (apply, notify, salva)
  • Código morto e inalcançável

Estado e mutabilidade

  • Variável mutável compartilhada (resultado)
  • Mutação direta de objetos (p.total, p.desconto)
  • Acoplamento estrutural profundo (p.c.inadimplente)

Side effects e imprevisibilidade

  • Side effects no meio da lógica de negócio
  • Comportamento não determinístico (Math.random() no core)

Esses problemas raramente aparecem isolados. Eles vêm juntos, e cada um torna o próximo mais provável.

Outros anti-patterns comuns em projetos reais

Além do código espaguete, existem outros padrões ruins que aparecem com frequência e causam dano de forma silenciosa:

Modelagem e contrato de dados

  • true/false puro em vez de um Result estruturado
  • Múltiplos tipos de retorno na mesma função
  • Objeto de resultado inflado com campos vazios (meta: {}, data: {})
  • Misturar domínio com transporte (ex: Result com statusCode junto)
  • Retorno com objeto anônimo sem contrato definido

UI e frontend acoplado errado

  • Fetch direto dentro do componente
  • Chamadas à API espalhadas sem apiClient centralizado
  • Regra de negócio dentro de useEffect
  • Transformação de dados acontecendo na UI
  • try/catch espalhado em toda a interface
  • Múltiplas fontes de verdade no state

Design de código

  • Funções genéricas demais (handle, process, manage)
  • Funções com mais de uma responsabilidade clara
  • Duplicação de estrutura entre camadas
  • Cache acoplado ao core
  • Abstração sem necessidade real

Fluxo e controle

  • Exceptions como controle de fluxo normal
  • Falta de early return, aninhamento desnecessário
  • Código morto ou inalcançável

O mesmo código, sem a tortura

Reescrito seguindo princípios simples: orquestrador no topo, detalhes agrupados com Lexical Scoping, density visual e nomes que explicam a intenção.

✅ Deixa eu ver o código narrativo…
// ✅ Código Narrativo
// Orquestrador no topo, detalhes agrupados (Lexical Scoping),
// density visual e nomes expressivos.

await realizaVenda(123);

async function realizaVenda(codigoDoPedido) {
  const detalhesDoPedido = buscaPedido(codigoDoPedido);
  if (pedidoInvalido(detalhesDoPedido)) return;

  const notaFiscalEmitida = emiteNotaFiscal(detalhesDoPedido);
  return notaFiscalEmitida;

  function pedidoInvalido(detalhesDoPedido) {
    if (detalhesDoPedido === null || detalhesDoPedido.itens.length === 0) return true;

    if (detalhesDoPedido.cliente.inadimplente) return notificaInadimplencia(detalhesDoPedido);

    return false;
  }

  function emiteNotaFiscal(detalhesDoPedido) {
    aplicaDescontos(detalhesDoPedido);
    const notaFiscal = salvaPedido(detalhesDoPedido);
    return notaFiscal;
  }
}

Praticamente o mesmo código. O que mudou foi a estrutura e a intenção. O fluxo principal cabe em quatro linhas e dá pra ler de cima pra baixo como uma sequência de decisões clara.

Os principais pontos do bom padrão

Nomes e legibilidade

  • Identificadores descritivos e com intenção clara
  • Consistência de idioma no código inteiro
  • Funções e variáveis autoexplicativas

Controle de fluxo

  • Early return para simplificar a leitura
  • Redução de aninhamento (flat code)
  • Condições explícitas (===, !==)

Retorno e contrato

  • Retorno consistente, sempre o mesmo formato
  • Contratos explícitos entre funções
  • Evitar null/undefined como controle de fluxo

Estrutura e design

  • Funções com responsabilidade única (SRP)
  • Separação clara entre domínio, persistência e side effects
  • Remoção de código morto

Estado e mutabilidade

  • Preferência por imutabilidade
  • const sempre que possível
  • Acesso via interfaces claras, baixo acoplamento

Side effects e previsibilidade

  • Side effects isolados e explícitos
  • Funções puras sempre que possível
  • Comportamento determinístico no core

Seguir convenções, princípios e metodologias como DDD, TDD, SOLID, YAGNI é importante, mas não é o foco principal. O foco principal é escrever código limpo e legível. As siglas vêm como consequência natural de um código bem cuidado.

Se o código que você está lendo hoje parece um labirinto, provavelmente virou um ao longo do tempo, uma decisão ruim de cada vez. O antídoto não precisa ser uma reescrita total. Às vezes é só a próxima decisão, tomada com mais cuidado.

Happy coding!

Referências

Ver todos os artigos →