O que é code smell e os sinais mais comuns de código problemático

Você abre um arquivo para corrigir um detalhe no cálculo de desconto e encontra uma função chamada processarPedido com 300 linhas, sete parâmetros e um comentário no meio dizendo “não mexer”. O código funciona. Os testes, quando existem, passam. Mesmo assim, algo ali cheira mal.
$ ls ./neste-artigo
- Tabela rápida dos cheiros mais comuns
- Método longo: a função que faz tudo
- Código duplicado em três lugares diferentes
- Lista longa de parâmetros e o aglomerado de dados
- Obsessão por tipos primitivos
- Comentário explicando o que o código deveria dizer sozinho
- Mudança espalhada: um ajuste pequeno, quinze arquivos editados
- Code smell, bug e dívida técnica não são a mesma coisa
- Quando um code smell pode ficar onde está
É exatamente isso que o termo descreve.
Para guardar: code smell é um sinal superficial no código que costuma indicar um problema mais fundo de desenho. Não é bug, não quebra nada hoje, mas torna o código difícil de entender e de mudar. A correção quase sempre é uma refatoração: mudar a estrutura sem mudar o comportamento.
A expressão foi popularizada por Kent Beck e Martin Fowler no livro Refactoring. Fowler resume a ideia no artigo Code Smell, de Martin Fowler: um cheiro é algo rápido de perceber, mas que nem sempre é um problema. Ele é um convite para investigar, não uma sentença. Este verbete faz parte do glossário de programação do buildbase, e aqui o formato é de diagnóstico: o sintoma que você vê, a causa provável e o tratamento.
Tabela rápida dos cheiros mais comuns
| Cheiro | Como reconhecer | Refatoração típica |
|---|---|---|
| Método longo | Função que não cabe na tela | Extract Function |
| Código duplicado | Mesma lógica em vários lugares | Extract Function, Pull Up Method |
| Classe grande | Dezenas de campos e métodos sem relação | Extract Class |
| Lista longa de parâmetros | Mais de quatro ou cinco argumentos | Introduce Parameter Object |
| Inveja de recurso (Feature Envy) | Método que usa mais dados de outra classe do que da sua | Move Function |
| Obsessão por primitivos | Números mágicos, strings soltas | Replace Primitive with Object |
| Nome misterioso | x, tmp2, handle() | Rename Variable, Rename Function |
Os nomes das refatorações estão no catálogo de refatorações de Martin Fowler, que traz cada uma com exemplo de código e passo a passo.
Método longo: a função que faz tudo
Sintoma: você precisa rolar a tela várias vezes para ler uma função inteira, e ela tem comentários separando “partes”.
Causa provável: a função foi crescendo a cada pedido novo. Ninguém parou para dividir, porque “era só mais um if“.
Tratamento: a refatoração Extract Function. Cada bloco que tinha um comentário explicando o que faz vira uma função com nome que diz o que faz. O comentário morre e o nome assume o papel dele.
# Antes
def fechar_pedido(pedido):
# calcula subtotal
subtotal = sum(i.preco * i.qtd for i in pedido.itens)
# aplica cupom
if pedido.cupom == "BEMVINDO":
subtotal *= 0.9
...
# Depois
def fechar_pedido(pedido):
subtotal = calcular_subtotal(pedido.itens)
subtotal = aplicar_cupom(subtotal, pedido.cupom)
...
Código duplicado em três lugares diferentes
Sintoma: você corrige uma regra (digamos, a validação de CPF) e descobre que ela existe copiada no cadastro, no checkout e na área do cliente. Corrigiu uma, esqueceu as outras duas.
De onde vem: copiar e colar é o caminho mais curto quando o prazo aperta.
Como resolver: extrair a lógica para um único lugar e chamar de todos os pontos. Mas atenção ao contrário: duas coisas parecidas nem sempre são a mesma coisa. Se a regra de frete do atacado e a do varejo só coincidem hoje, juntar as duas pode criar um acoplamento que vai doer quando uma delas mudar. Muitos devs usam a “regra de três”: na terceira repetição, aí sim vale extrair.

Lista longa de parâmetros e o aglomerado de dados
Como aparece: funções como criarEndereco(rua, numero, complemento, bairro, cidade, uf, cep), e os mesmos cinco ou seis valores viajando juntos por várias chamadas.
Origem: falta um objeto que represente o conceito. No catálogo de Fowler, esse grupo que sempre anda junto tem nome: Data Clumps.
Saída: criar uma classe ou estrutura (Endereco) e passar ela inteira. Bônus: a validação do CEP passa a morar em um lugar só.
Obsessão por tipos primitivos
Como se manifesta: dinheiro guardado em float, telefone como string solta, status de pedido como número mágico (if status == 3).
Por que acontece: parece mais simples usar o tipo básico da linguagem do que criar algo novo.
Correção: substituir o primitivo por um tipo com significado. Um enum StatusPedido { PAGO, ENVIADO, ENTREGUE } elimina o “3” que ninguém lembra o que é. Um tipo Dinheiro que trabalha com centavos inteiros elimina o famoso 0,1 + 0,2 = 0,30000000000000004.
Comentário explicando o que o código deveria dizer sozinho
Sinal: blocos como // verifica se o usuário pode comprar antes de uma condição de três linhas com && e ||.
O que está por trás: o autor sabia que o trecho estava obscuro e tentou compensar com texto.
Ajuste: extrair a condição para uma função ou variável com nome claro: if (podeComprar(usuario)). Comentário bom explica o porquê de uma decisão estranha (“a API do banco devolve 200 mesmo com erro, por isso checamos o corpo”), não o o quê.
Mudança espalhada: um ajuste pequeno, quinze arquivos editados
Na prática: para adicionar um novo método de pagamento, você mexe no controller, em dois serviços, no repositório, em três templates e num arquivo de configuração. Fowler chama isso de Shotgun Surgery, cirurgia de espingarda.
Raiz do problema: uma responsabilidade que deveria estar concentrada ficou espalhada.
Refatoração indicada: mover funções e campos para perto uns dos outros (Move Function, Move Field) até que a mudança típica toque um só módulo. O irmão desse problema é a Divergent Change: uma única classe que muda por motivos completamente diferentes (layout de relatório e regra de imposto no mesmo arquivo).
Code smell, bug e dívida técnica não são a mesma coisa
Iniciantes misturam os três termos com frequência, e a diferença muda a prioridade do trabalho:
- Bug é comportamento errado: o sistema faz algo diferente do esperado. Corrige-se já.
- Code smell é estrutura suspeita: o comportamento está certo, mas o código é difícil de ler ou mudar. Corrige-se quando for mexer ali.
- Dívida técnica é o custo acumulado de atalhos e más estruturas, incluindo vários cheiros. Gerencia-se com planejamento, reservando tempo para pagá-la.
Na prática, um cheiro ignorado por muito tempo aumenta a chance de bugs, porque código confuso é código em que se erra mais. Uma função com cinco níveis de if aninhado é o ambiente perfeito para um caso de borda escapar.
Ferramentas de análise estática, como SonarQube, ESLint (com regras de complexidade) ou Pylint, apontam vários desses cheiros automaticamente: complexidade ciclomática alta, funções longas, parâmetros demais, código duplicado. Elas não substituem o julgamento de quem conhece o projeto, mas servem como um detector de fumaça que roda a cada commit.
Quando um code smell pode ficar onde está
Nem todo cheiro precisa ser eliminado agora. Um script que roda uma vez para migrar dados, um protótipo que vai para o lixo na semana que vem ou um módulo legado que ninguém toca há anos podem conviver com seus cheiros sem prejuízo real.
A pergunta que decide é: alguém vai precisar mudar esse código em breve? Se sim, refatore antes de mexer (a chamada regra do escoteiro: deixe o código um pouco melhor do que encontrou). Se não, anote e siga em frente.
E refatore com rede de proteção. Sem testes cobrindo o comportamento atual, “melhorar a estrutura” vira “torcer para não ter quebrado nada”. Antes de dividir aquela função de 300 linhas, escreva dois ou três testes que confirmem o que ela devolve hoje.
Para ir além do diagnóstico, nosso guia de boas práticas de Clean Code para iniciantes mostra os hábitos que evitam que boa parte desses cheiros apareça. Minha recomendação para quem está começando: escolha um único cheiro da tabela, procure por ele no projeto em que você trabalha esta semana e corrija um caso, com teste antes. Treinar o nariz em um cheiro de cada vez funciona melhor do que tentar decorar o catálogo inteiro.




