Pular para o conteúdo
11 min de leitura

Code smells: reconhecendo sinais de código problemático

Por Lucas Andrade ·

Code smell é sintoma, não bug: o código funciona mas cheira mal. Conheça o catálogo dos sinais mais comuns e por que eles convidam a investigar e refatorar.

Neste artigo

Há um tipo de conhecimento que desenvolvedores experientes carregam e que é difícil de transmitir: a capacidade de olhar um trecho de código que funciona perfeitamente e, ainda assim, sentir que há algo errado ali. Não é um bug, nada quebra, todos os testes passam, mas existe um incômodo, uma sensação de que aquele código vai dar trabalho, de que mexer nele vai ser doloroso, de que ele esconde problemas que ainda não se manifestaram. Esse faro tem um nome consagrado na engenharia de software: code smell, ou "cheiro de código".

A metáfora do cheiro é precisa. Um cheiro ruim na cozinha não é a comida estragada em si; é o indício de que algo pode estar estragando. Da mesma forma, um code smell não é um defeito, é um sinal na superfície do código que sugere um problema mais profundo na estrutura. Ele não prova que há algo errado, mas convida a investigar. Ignorar cheiros consistentemente é como ignorar o cheiro de gás: talvez não haja perigo, mas a aposta é ruim, e a hora de olhar é antes, não depois do estrago.

Este artigo apresenta a ideia de code smell como sintoma e não como bug, e percorre um catálogo dos cheiros mais comuns que aparecem no dia a dia. O objetivo não é transformar cada cheiro em uma regra rígida de proibição, mas afinar a percepção de quem lê código, para que os sinais sejam notados e investigados. Vamos ver por que os smells guiam a refatoração, e por que aprender a senti-los é um passo importante na maturidade de qualquer pessoa que escreve software.

O que é um code smell#

A distinção fundamental é entre bug e smell. Um bug é um defeito: o código faz algo errado, produz um resultado incorreto, quebra em alguma situação. Um smell é diferente; o código funciona, entrega o resultado esperado, mas sua estrutura tem características que costumam estar associadas a dificuldades futuras. O smell é sobre a forma, não sobre o comportamento. Ele diz "isto vai ser difícil de manter, de entender ou de estender", não "isto está errado agora".

Por serem sinais e não defeitos, os code smells são heurísticos, não determinísticos. A presença de um cheiro não garante que há um problema; ela apenas eleva a probabilidade e recomenda um olhar mais atento. Há situações em que um trecho com todas as características de um smell é, de fato, a melhor solução possível para aquele problema específico, e forçar uma mudança só pioraria. Por isso, o smell é um convite à investigação, e não um veredito de culpa. Ele diz "olhe aqui", e cabe ao julgamento humano decidir se há algo a corrigir.

Essa natureza heurística é o que torna os smells uma ferramenta tão poderosa e, ao mesmo tempo, tão fácil de usar mal. Usados bem, eles direcionam a atenção para os pontos da base de código que mais provavelmente escondem problemas, economizando o esforço de revisar tudo com o mesmo escrutínio. Usados como dogma, viram uma lista de proibições mecânicas que geram discussões estéreis e mudanças que pioram o código em nome de eliminar um cheiro que, naquele caso, era inofensivo. O valor está em usá-los como bússola, não como lei.

Cheiros de tamanho e de responsabilidade#

Um primeiro grupo de smells tem a ver com peças que cresceram além do que deveriam, acumulando responsabilidades que pediam separação.

  • Função longa. A função que se estende por dezenas ou centenas de linhas quase sempre está fazendo coisas demais. Ela mistura vários níveis de abstração, junta etapas que poderiam ser independentes e obriga o leitor a percorrer um bloco enorme para entender o todo. O comprimento em si é o sintoma; a doença provável é a falta de decomposição em funções menores e bem nomeadas, cada uma com um propósito único.
  • Classe grande. O equivalente da função longa no nível da classe. Uma classe que acumula atributos e métodos demais tende a estar concentrando responsabilidades que deveriam estar distribuídas. Ela vira um ponto de acoplamento por onde tudo passa, difícil de entender por inteiro e arriscada de modificar, porque qualquer mudança pode afetar muitas coisas que só estão juntas por acidente histórico.
  • Lista longa de parâmetros. Quando uma função recebe muitos parâmetros, fica difícil lembrar a ordem, fácil trocar um pelo outro e cansativo chamá-la. Frequentemente, uma lista longa de parâmetros indica que vários deles andam sempre juntos e deveriam ser agrupados em uma estrutura própria, ou que a função está fazendo trabalho demais e deveria ser dividida.

O denominador comum desses cheiros é o excesso de responsabilidade concentrada em uma peça só. Eles apontam, quase sempre, na direção da decomposição: quebrar o grande em partes menores e coesas, cada uma com um nome que diz o que faz e um escopo que cabe na cabeça. A refatoração de extração é a resposta natural para a maioria deles.

Cheiros de duplicação e de dependência#

Um segundo grupo de cheiros tem a ver com a forma como as partes do código se relacionam e se repetem.

  • Código duplicado. A repetição da mesma lógica em vários lugares é um dos cheiros mais clássicos. Ele preocupa porque significa que uma mudança de regra terá de ser feita em vários pontos, com risco de esquecer algum e gerar inconsistência. Vale lembrar, porém, que nem toda semelhança é duplicação verdadeira; o cheiro convida a distinguir a repetição de conhecimento genuíno, que pede unificação, da coincidência superficial, que às vezes é melhor manter separada.
  • Inveja de recursos. Ocorre quando um trecho de código se interessa mais pelos dados de outro objeto do que pelos seus próprios, acessando repetidamente atributos alheios para tomar decisões. Esse cheiro sugere que a lógica está no lugar errado, que ela deveria morar junto dos dados que manipula. Mover o comportamento para perto dos dados costuma ser a cura.
  • Acoplamento excessivo. Quando dois módulos conhecem demais um do outro, dependem de detalhes internos mútuos e não podem mudar de forma independente, há acoplamento em excesso. Ele torna o sistema rígido: uma mudança em um ponto exige mudanças em cascata em outros, e o medo de mexer se instala. O cheiro aponta para a necessidade de fronteiras mais claras e de interfaces que escondam os detalhes internos.

Esses cheiros falam da saúde das relações entre as partes. Um sistema saudável tem partes coesas por dentro e fracamente acopladas entre si; os cheiros deste grupo denunciam justamente o oposto, partes que se enredam umas nas outras e que repetem conhecimento que deveria ter uma fonte única. Refatorar aqui significa reorganizar responsabilidades e reforçar fronteiras.

Cheiros de modelagem e de expressão#

Um terceiro grupo tem a ver com a forma como o problema foi modelado e como a lógica foi expressa.

  • Obsessão por primitivos. Aparece quando conceitos do domínio são representados por tipos primitivos crus, como textos e números soltos, em vez de por tipos próprios que capturam seu significado e suas regras. Um valor que é, na verdade, um conceito rico do negócio, mas que trafega pelo sistema como uma string qualquer, espalha validações e interpretações por toda parte, quando poderia concentrá-las em um tipo dedicado. O cheiro convida a dar corpo aos conceitos do domínio.
  • Condicionais complexas. Expressões booleanas emaranhadas, com muitos operadores encadeados, e cadeias longas de decisões aninhadas são difíceis de ler e de verificar. Elas escondem a lógica de negócio em um cipoal de condições que ninguém consegue avaliar com confiança. O cheiro sugere extrair predicados nomeados, simplificar com retornos antecipados ou substituir cadeias de decisão por estruturas mais claras.
  • Comentários excessivos. Paradoxalmente, o excesso de comentários é um cheiro. Quando um trecho precisa de muita explicação em prosa para ser compreendido, isso costuma indicar que o próprio código não está comunicando bem, e que a explicação deveria migrar para dentro dele, na forma de nomes melhores e de estrutura mais clara. O comentário, aqui, é o sintoma de um código que não se explica sozinho.

Esses cheiros tratam da distância entre o código e o problema que ele resolve. Quando essa distância é grande, o código fica obscuro, cheio de traduções mentais que o leitor precisa fazer. Refatorar significa aproximar o código do domínio, dando nome aos conceitos, tornando as decisões explícitas e deixando a estrutura falar por si.

Cheiros de resíduo e de rigidez#

Um quarto grupo reúne cheiros que sinalizam código que sobrou, que enrijeceu ou que envelheceu mal.

  • Código morto. Trechos que não são mais executados, funções que ninguém chama, ramos que nunca são alcançados, variáveis que não são usadas. Esse resíduo confunde a leitura, porque quem lê não sabe se aquilo é importante e perde tempo entendendo algo irrelevante, e ainda mascara a real superfície do sistema. O código morto deve ser removido; o histórico de versão guarda o que um dia existiu, e o arquivo deve refletir apenas o que vive.
  • Generalidade especulativa. É o excesso de flexibilidade construído para necessidades que nunca chegaram. Camadas de abstração, pontos de extensão e parâmetros de configuração criados "para o caso de precisar" acabam pesando sobre o código sem entregar valor, porque a necessidade prevista não se concretizou. O cheiro sugere remover a flexibilidade não usada e voltar à simplicidade que o requisito real pede.
  • Mudança divergente e cirurgia de espingarda. Dois cheiros complementares de organização. No primeiro, uma única peça precisa ser alterada por muitos motivos diferentes, sinal de que ela concentra responsabilidades demais. No segundo, uma única mudança conceitual obriga a tocar muitas peças espalhadas, sinal de que uma responsabilidade que deveria estar concentrada foi fragmentada. Ambos apontam para uma distribuição errada de responsabilidades pelo sistema.

Esses cheiros lembram que código também acumula entulho e endurece com o tempo. A refatoração associada a eles é, em parte, faxina, remover o que não serve, e, em parte, reorganização, colocar cada responsabilidade em um lugar que faça as mudanças futuras caírem em pontos previsíveis. Um sistema bem organizado é aquele em que mudanças esperadas afetam poucos lugares previsíveis, e os cheiros deste grupo denunciam quando essa propriedade se perdeu.

Como os cheiros guiam a refatoração#

O valor prático dos code smells está em servir de guia para a refatoração. Refatorar sem direção, mexendo no código só pela sensação de que ele poderia estar melhor, é ineficiente e arriscado. Os cheiros dão foco: eles apontam onde a estrutura provavelmente está frágil e qual tipo de melhoria tende a resolver aquele sintoma específico. Cada cheiro tem, em geral, um conjunto de refatorações que costumam curá-lo, o que transforma a percepção de um problema em um plano de ação.

O fluxo saudável é: perceber o cheiro, investigar se ele realmente indica um problema naquele contexto, e, em caso positivo, aplicar a refatoração apropriada em passos pequenos e seguros. O primeiro passo, perceber, depende do faro treinado que este catálogo ajuda a desenvolver. O segundo, investigar, é o que impede que os smells virem dogma, porque reconhece que nem todo cheiro exige ação. O terceiro, refatorar, é onde o sintoma vira melhoria concreta, sempre com a rede de segurança dos testes para garantir que o comportamento não mudou.

É importante manter a proporção. Nem todo cheiro precisa ser corrigido imediatamente, e uma base de código real sempre terá alguns. A decisão de refatorar leva em conta o custo da mudança, o risco, e a probabilidade de que aquele trecho vá precisar ser tocado no futuro. Um cheiro em código estável, que ninguém mexe, tem prioridade baixa; o mesmo cheiro em código central, que muda toda semana, merece atenção urgente, porque a fricção que ele causa se paga muitas vezes. Os smells ajudam a priorizar, e não apenas a apontar.

No fim, aprender a reconhecer code smells é aprender a ler código com um olhar crítico e antecipatório, enxergando não só o que ele faz hoje, mas como ele vai se comportar diante das mudanças de amanhã. Esse olhar não nasce pronto; ele se constrói com a experiência de manter sistemas ao longo do tempo, de sentir na pele a dor de um código que resistiu a ser modificado, e de aos poucos associar certas formas na superfície a certas dores no futuro. O catálogo de cheiros é um atalho para esse aprendizado, uma forma de herdar a intuição acumulada por gerações de desenvolvedores que já sentiram esses mesmos incômodos e lhes deram nome. Cultivar esse faro é uma das marcas mais confiáveis de quem deixou de apenas fazer o código funcionar e passou a se importar com o quanto ele vai durar bem.

Leituras relacionadas

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly