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 (
ifdentro deifdentro deif) - 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/falsepuro 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:
ResultcomstatusCodejunto) - Retorno com objeto anônimo sem contrato definido
UI e frontend acoplado errado
- Fetch direto dentro do componente
- Chamadas à API espalhadas sem
apiClientcentralizado - Regra de negócio dentro de
useEffect - Transformação de dados acontecendo na UI
try/catchespalhado 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/undefinedcomo 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
constsempre 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 →