Os "code smells" não são bugs. Um programa com bugs trava, retorna resultados incorretos ou falha em testes. Um programa com "code smells" pode funcionar perfeitamente bem por anos, e ainda assim, cada alteração nele custa mais do que deveria, cada novo recurso acarreta riscos inesperados e cada tentativa de refatoração revela dependências desconhecidas. Os "code smells" são características estruturais do código que preveem problemas futuros: eles não causam uma falha imediata, mas tornam cada alteração futura mais difícil, lenta e perigosa do que o necessário.
O termo foi popularizado por Martin Fowler e Kent Beck em seu livro " Refactoring: Improving the Design of Existing Code " (1999), que catalogou 22 tipos de código problemáticos e associou cada um a uma técnica de refatoração correspondente. Esse catálogo permanece como a referência canônica, e os tipos de código problemáticos que Fowler nomeou — Método Longo, Classe Deus, Código Duplicado, Inveja de Funcionalidade, Mudança Divergente, Cirurgia de Espingarda, entre outros — aparecem em conjuntos de regras do SonarQube, ferramentas de análise estática e listas de verificação de revisão de código em toda a indústria atualmente.
Limpe os odores do código
SMART TS XL ajuda a mapear e corrigi-los em sistemas complexos.
Mais informaçõesO que é um "code smell"?
Um "code smell" (cheiro de código) é uma característica superficial do código-fonte que sugere um problema estrutural ou de design mais profundo. O código compila, passa nos testes e produz a saída correta, mas algo em sua estrutura torna mais difícil lê-lo, estendê-lo ou modificá-lo com segurança do que deveria. Definição de Fowler: "uma indicação superficial que geralmente corresponde a um problema mais profundo no sistema".
Os "code smells" não são violações no mesmo sentido que um erro de sintaxe ou uma asserção falha. São indicadores, padrões que desenvolvedores experientes reconhecem como sinais de alerta, mesmo quando nenhuma falha imediata é visível. O perigo reside no fato de serem cumulativos: um único método longo em uma base de código de 10,000 linhas é um pequeno inconveniente. Centenas de métodos longos, lógica duplicada espalhada por dezenas de módulos e classes "deus" no centro do grafo de dependências criam um sistema que se torna genuinamente difícil de modificar com segurança.
Código com mau cheiro vs. Bugs vs. Dívida técnica
Esses três conceitos estão relacionados, mas são distintos, e confundi-los leva a uma priorização inadequada:
| O Conceito | Definição | Falha imediata? | Como encontrar |
|---|---|---|---|
| Bug | Código que produz comportamento incorreto | Sim, os testes falham, os usuários relatam erros. | Testes, monitoramento, registros de erros |
| Cheiro de código | Padrão estrutural que prevê problemas futuros | Não, o código é executado corretamente. | Revisão de código, análise estática |
| Dívida técnica | O custo acumulado de atalhos e decisões ruins do passado. | Não, mas se acumula ao longo do tempo. | Métricas, análise de complexidade, estimativas de esforço de refatoração |
Os "code smells" são o mecanismo pelo qual a dívida técnica se acumula. Cada método longo adicionado à base de código representa uma unidade de dívida técnica; o pagamento de juros corresponde ao tempo extra que cada desenvolvedor futuro gasta para entendê-lo e que cada alteração futura gasta para evitar os efeitos colaterais de seu tamanho.
O que é um "code smell" no SonarQube?
O SonarQube classifica problemas de código em três categorias: bugs (definitivamente errados), vulnerabilidades (problemas de segurança) e code smells (problemas de manutenibilidade). Os code smells do SonarQube correspondem diretamente ao catálogo de Fowler e incluem regras para métodos longos (acima de limites de linhas configuráveis), blocos duplicados, excesso de parâmetros, pontuações de complexidade cognitiva complexas, ausência de tratamento de erros e violações de acoplamento arquitetural. As regras de code smells do SonarQube são a operacionalização automatizada mais amplamente utilizada na indústria da taxonomia original de Fowler.
O Código Cheira a Código de Martin Fowler: A Taxonomia Clássica
Os 22 "code smells" originais de Fowler, organizados por categoria, continuam sendo a referência padrão. Todas as principais ferramentas de análise estática têm seus conjuntos de regras derivados dessa taxonomia.
| Categoria | Cheiro de código |
|---|---|
| Inchaço, código que cresceu a um tamanho incontrolável | Método longo, Classe grande, Obsessão por primitivos, Lista longa de parâmetros, Agrupamentos de dados |
| Abusadores da orientação a objetos, uso indevido dos princípios de OO | Instruções Switch, Campo Temporário, Legado Recusado, Classes Alternativas com Interfaces Diferentes |
| Prevenção de mudançasdificultar a mudança | Mudança Divergente, Cirurgia de Espingarda, Hierarquias de Herança Paralela |
| Materiais descartáveis, código desnecessário | Comentários (excessivos), Código Duplicado, Classe Preguiçosa, Classe de Dados, Código Morto, Generalidade Especulativa |
| acopladoresacoplamento excessivo | Inveja de recursos, intimidade inapropriada, correntes de mensagens, intermediário |
Entender a qual categoria um problema pertence ajuda a priorizar a correção: Problemas que incham e problemas que impedem mudanças estão diretamente relacionados a altos custos de refatoração; Problemas que acoplam estão diretamente relacionados à fragilidade da arquitetura; Problemas dispensáveis são os mais seguros para remover.
Os erros de código mais comuns: Guia rápido
| Cheiro de código | O que isso parece | Risco Primário |
|---|---|---|
| Código duplicado | A mesma lógica aparece em vários lugares. | As correções de bugs devem ser aplicadas em todos os lugares; as cópias divergem com o tempo. |
| Método longo | Métodos com mais de 20 a 30 linhas e múltiplas responsabilidades. | Alta carga cognitiva; dificuldade em testar comportamentos isolados. |
| Classe Divina / Classe Grande | Uma aula que faz tudo | Cada alteração de funcionalidade afeta a mesma classe; conflitos de mesclagem, fragilidade. |
| Lista longa de parâmetros | Métodos que utilizam 4 ou mais parâmetros | É fácil passar valores incorretos; os locais de chamada são difíceis de ler. |
| Inveja de recurso | Um método que utiliza dados de outra classe em vez de dados da sua própria classe. | Acoplamento forte; a mudança em uma classe afeta a outra. |
| Mudança Divergente | Uma classe modificada por vários motivos diferentes. | Viola a responsabilidade individual; efeitos colaterais imprevisíveis. |
| Cirurgia de espingarda | Uma única alteração exige edições em várias classes. | Alto custo de alteração; fácil perder uma instância. |
| Código morto | Código que nunca é chamado ou alcançado | Confunde os desenvolvedores; acumula-se ao longo dos anos; complica a migração. |
| Obsessão primitiva | Utilizando tipos básicos (strings, ints) em vez de objetos de domínio. | Validação dispersa por toda parte; pouca expressividade. |
| Agrupamentos de dados | O mesmo grupo de campos passou junto repetidamente. | Deveria ser um objeto de domínio; sinaliza falta de abstração. |
| Generalidade Especulativa | Código escrito para necessidades futuras imaginadas | Complexidade desnecessária; ninguém entende por que está ali. |
| Tratamento de erros inconsistentes | Capturas silenciosas, estratégias de exceção variáveis | As falhas passam despercebidas; a depuração demora muito mais tempo. |
Definições e exemplos de Code Cheat
Código duplicado
O problema de código mais comum e mais caro em sistemas de grande porte. A duplicação surge do desenvolvimento por copiar e colar, da pressão do tempo e de equipes trabalhando isoladamente que resolvem o mesmo problema de forma independente. A consequência imediata é um custo de manutenção: cada alteração na lógica compartilhada precisa ser aplicada a todas as cópias.
Java
// ServiceA -- discount calculation
double calculateDiscount(double amount) {
if (amount > 1000) return amount * 0.1;
return 0;
}
// ServiceB -- same logic, copied and forgotten
double computeDiscount(double value) {
if (value > 1000) return value * 0.1;
return 0;
}
Quando a regra de negócio muda (o limite passa a ser 1500, a taxa passa a ser 12%), uma cópia é atualizada e a outra não. Dois módulos agora divergem em relação à lógica de negócio fundamental, e a discrepância surge em produção durante uma auditoria, em vez de ser detectada em testes.
Correção : Extraia a lógica compartilhada para uma única função, classe utilitária ou biblioteca compartilhada que seja referenciada por ambos os chamadores.
Método longo
Um método que se expandiu para além de seu propósito original, absorvendo responsabilidades adicionais ao longo do tempo. A carga cognitiva de ler um método de 200 linhas é qualitativamente diferente da leitura de vinte métodos de 10 linhas, e não apenas quantitativamente. Métodos longos são difíceis de testar porque executam muitas tarefas para serem testadas isoladamente, e difíceis de entender porque o leitor precisa manter todo o contexto de execução na memória de trabalho.
Limiar de detecção : Métodos com mais de 20 a 30 linhas justificam revisão; acima de 50 linhas, a refatoração é quase sempre justificada. Em COBOL, parágrafos com mais de 100 instruções são o equivalente.
python
class OrderProcessor:
def process_order(self, order):
# Validate order -- 40 lines
# Calculate discounts -- 30 lines
# Update inventory -- 25 lines
# Send notification emails -- 20 lines
# Generate invoice -- 35 lines
# 150+ lines total
pass
Nesse método, cada responsabilidade deve ser uma classe ou função separada. Agrupá-las significa que qualquer atualização futura de faturamento, estoque ou notificações corre o risco de desestabilizar todo o fluxo de processamento de pedidos.
Aula de Deus
Uma classe que acumulou responsabilidades em múltiplos domínios, violando o Princípio da Responsabilidade Única de forma tão severa que se torna o centro de gravidade de uma base de código: tudo depende dela, e alterar qualquer coisa nela exige compreender tudo sobre ela.
Sinal de detecção : Uma classe com mais de 20 a 30 métodos públicos, ou cujo nome contenha "Manager", "Processor", "Handler", "Utils" ou "Helper" aplicados a múltiplos domínios não relacionados.
Mudança Divergente
Uma classe que é modificada por diversos motivos diferentes e não relacionados. Sempre que o esquema do banco de dados muda, você edita essa classe. Sempre que as regras de precificação mudam, você edita essa classe. Sempre que o formato da notificação muda, você edita essa classe. Essa classe tem muitas responsabilidades e deveria ser dividida.
Definição : Uma classe que está em constante mudança por diferentes motivos. O inverso da cirurgia de espingarda.
Cirurgia de espingarda
Uma única alteração conceitual que exige edições em diversas classes diferentes. Alterar uma taxa de imposto requer a modificação de um cálculo no backend, uma validação no frontend, um gatilho no banco de dados, um processo em lote e uma consulta de relatório, em cinco locais diferentes. A omissão de qualquer um deles resulta em comportamento inconsistente.
sql
-- Tax logic duplicated across queries
SELECT amount * 0.05 FROM invoices;
SELECT amount * 0.05 FROM payments;
SELECT amount * 0.05 FROM reports;
Alterar de 0.05 para 0.07 agora exige encontrar todas as ocorrências em arquivos SQL, procedimentos armazenados e código do aplicativo.
Inveja de recurso
Um método que gasta mais tempo usando dados e métodos de outra classe do que os seus próprios. Isso indica que o comportamento provavelmente pertence à outra classe.
Java
// In ReportGenerator -- envious of Customer's data
double calculateCustomerRating(Customer customer) {
return customer.getOrderCount() * customer.getAverageOrderValue()
/ customer.getDaysSinceRegistration();
}
// This logic belongs in Customer, not ReportGenerator
Código morto
Código que existe no repositório, mas nunca é executado em produção. Código morto se acumula ao longo dos anos à medida que funcionalidades são removidas, substituídas ou reestruturadas sem que o código antigo seja excluído. Isso gera ruído na revisão de código, confunde os desenvolvedores que estão se integrando à base de código, complica a análise de migração e, ocasionalmente, é reativado acidentalmente.
DetecçãoFerramentas de análise estática, incluindo SonarQube, Knip (para TypeScript/JavaScript) e SMART TS XL Identificar funções inacessíveis, métodos não chamados e variáveis não utilizadas em toda a base de código.
Violações do Princípio DRY
O princípio "Não se Repita" (Don't Repeat Yourself - DRY) afirma que cada informação deve ter uma única representação inequívoca dentro de um sistema. Violações do princípio DRY são a causa principal de código duplicado, aglomerados de dados e muitos cenários de "cirurgia de espingarda". Quando a lógica de negócios é representada em múltiplos locais, essas representações inevitavelmente divergem. DRY é o princípio; código duplicado é o sinal que indica a violação.
python
# DRY violation: same validation logic in three places
def validate_email_in_registration(email):
return "@" in email and "." in email
def validate_email_in_profile_update(email):
return "@" in email and "." in email
def validate_email_in_checkout(email):
return "@" in email and "." in email
# DRY-compliant: one function, three callers
def is_valid_email(email):
return "@" in email and "." in email
Limiares de detecção: quando o código se torna um mau cheiro?
A detecção de códigos defeituosos requer limites mensuráveis. Abaixo estão as métricas comumente usadas e os valores que indicam um código defeituoso que precisa de atenção:
| métrico | O que ele mede | Limite de aviso | Limite Crítico |
|---|---|---|---|
| Complexidade ciclomática | Número de ramificações de decisão em um método | Acima 10 | Acima 20 |
| Comprimento do método (linhas) | Número de linhas em um método/função | Acima 20 | Acima 50 |
| Contagem de parâmetros | Número de parâmetros que um método aceita | Acima 4 | Acima 7 |
| Duração da aula | Número de linhas em uma classe | Acima 200 | Acima 500 |
| Taxa de duplicação | Percentagem de código duplicado | Acima de 3% | Acima de 10% |
| Complexidade Cognitiva | Quão difícil é entender o código? | Acima 15 | Acima 25 |
| Acoplamento aferente (Ca) | Número de classes que dependem desta classe | Acima 15 | Acima 30 |
| Acoplamento Eferente (Ce) | Número de classes das quais esta classe depende | Acima 15 | Acima 30 |
Esses limites são configuráveis no SonarQube, e a maioria das plataformas de análise estática permite regras personalizadas com base nessas métricas. As classes e os métodos com limite crítico são os alvos de refatoração de maior prioridade: são as fontes mais prováveis de defeitos futuros e os componentes mais caros de manter.
Ferramentas de detecção de código ruim
A detecção automatizada é a única abordagem escalável para identificar problemas de código em grandes bases de código. A revisão manual detecta apenas uma fração do que as ferramentas automatizadas encontram e não é escalável para sistemas legados com milhões de linhas de código.
| ferramenta | Idioma principal | O que ele detecta |
|---|---|---|
| SonarQube / SonarCloud | Java, Python, JS/TS, C# e muito mais | Taxonomia completa de odores de Fowler, pontos críticos de segurança, duplicações |
| Checkstyle + PMD | Java | Violações de estilo, duplicações, métricas de complexidade |
| ESLint + typescript-eslint | JavaScript, TypeScript | Funções longas, complexidade, código não utilizado |
| Pilina + Radônio | Python | Índice de complexidade, estilo e manutenibilidade |
| ReSharper / Rider | C# | Código redundante, métodos longos, problemas de acoplamento |
| Clippy | Ferrugem | Violações idiomáticas, padrões comuns que são sinais de alerta em Rust. |
| Código Clima | Multi-linguagem | Complexidade, duplicação, pontuação de manutenibilidade |
| SMART TS XL | COBOL, JCL, Java, Python, RPG, SQL, .NET | Duplicação entre linguagens, código morto, acoplamento, deriva de dependências |
Código com cheiro ruim em Rust são detectados principalmente pelo Clippy, que impõe padrões idiomáticos do Rust. Os "cheiros" mais comuns específicos do Rust incluem clonagem desnecessária, uso indevido de unwrap() Em caminhos de produção, expressões de correspondência excessivamente aninhadas e funções que deveriam retornar Result mas use pânico em vez disso.
Code Cheats e Dívida Técnica: A Conexão
A dívida técnica é o custo acumulado de decisões passadas que priorizaram a velocidade em detrimento da qualidade. Os "code smells" (cheiros de código) são o mecanismo pelo qual essa dívida se manifesta na estrutura do código. A relação é direta: cada "code smell" não corrigido representa uma unidade de dívida técnica, e a taxa de juros é o tempo extra que cada alteração futura precisa investir para contorná-lo.
Conforme descrito no contexto da análise de impacto para gerenciamento de mudanças de software , os problemas estruturais indicados pelos "code smells" — acoplamento excessivo, lógica duplicada e acúmulo de código morto — aumentam diretamente o escopo de cada mudança, pois dificultam a identificação dos impactos que uma determinada mudança terá.
Explique a dívida técnica em termos de "cheiros de código" : se uma base de código tem 40% de duplicação, cada correção de bug custa 1.4 vezes mais do que deveria. Se a classe principal de processamento é uma classe "deusa" da qual tudo depende, cada adição de recurso exige a compreensão e o teste de toda a classe. Se o tratamento de erros é inconsistente, cada incidente em produção exige mais tempo de investigação porque os sinais de falha são pouco confiáveis. A dívida técnica não é abstrata, é a soma dessas ineficiências cumulativas.
A pesquisa da CISQ constata consistentemente que os desenvolvedores gastam de 30 a 40% do seu tempo resolvendo problemas de dívida técnica em vez de desenvolver novas funcionalidades. A densidade de "code smells" (cheiro de código) é a medida mais direta da quantidade de dívida acumulada.
Como SMART TS XL Detecta problemas de código em escala empresarial
Ferramentas individuais como SonarQube e Clippy operam em uma única linguagem. Em ambientes corporativos onde programas COBOL gravam em conjuntos de dados que são lidos por serviços Java, onde fluxos de tarefas JCL invocam programas em múltiplas linguagens e onde a mesma lógica de negócios foi duplicada independentemente em três sistemas diferentes escritos em três décadas diferentes, ferramentas de linguagem única não conseguem enxergar o panorama completo.
SMART TS XL'S análise de código estático Detecta problemas de código em todas as linguagens do ambiente simultaneamente: lógica duplicada entre um copybook COBOL e uma classe utilitária Java, código morto em programas RPG que nenhum job JCL invoca, padrões de classe "Deus" em programas COBOL onde um único parágrafo faz o trabalho de cinquenta e padrões inconsistentes de tratamento de erros entre as diferentes linguagens.
A funcionalidade de mapeamento de dependências de aplicações identifica os problemas arquitetônicos que ferramentas individuais de nível de arquivo não conseguem detectar: quais componentes têm o maior acoplamento aferente (os mais dependentes, com maior risco de causar problemas quando alterados), onde existem dependências circulares entre módulos que deveriam ser independentes e onde a lógica de negócios duplicada foi mantida independentemente em sistemas diferentes, sem que nenhuma das cópias tivesse conhecimento da outra.
A capacidade de análise de impacto torna os problemas de código acionáveis: antes de refatorar qualquer componente com alto acoplamento, a análise de impacto enumera todos os componentes dependentes que precisam ser testados, validados ou atualizados. Isso transforma a "paralisia da refatoração" que as equipes vivenciam em grandes bases de código problemáticas em um programa de remediação estruturado e com escopo definido, onde cada alteração tem um escopo definido em vez de um risco desconhecido.
Para equipes que conduzem programas de modernização de sistemas legados , a análise de "code smells" (cheiro de código) é a base do plano de modernização: o código morto é eliminado antes do início da migração (reduzindo o escopo), a lógica duplicada é consolidada em implementações canônicas, os componentes com maior acoplamento são modernizados por último (após tudo que depende deles ter sido resolvido) e as classes "deusas" são decompostas antes de serem convertidas para uma nova linguagem, porque converter uma classe "deusa" para Java produz uma classe "deusa" em Java.
Abordando problemas de código: uma estrutura de priorização
Nem todos os problemas de código justificam uma refatoração imediata. A abordagem correta é a priorização baseada no risco:
Prioridade 1: Problemas em componentes com alta taxa de mudança. Códigos que mudam frequentemente e apresentam alta complexidade ou acoplamento geram mais defeitos. Esses componentes têm o maior custo por alteração e geram mais incidentes em produção. Corrija-os primeiro.
Prioridade 2: Problemas nas fronteiras da arquitetura. Classes "deus" e componentes altamente acoplados dos quais tudo depende são os mais perigosos de alterar, mas também os mais custosos de deixar sem correção. Esses exigem uma análise de impacto extremamente cuidadosa antes da refatoração.
Prioridade 3: Código duplicado entre sistemas. Quando a mesma lógica de negócios existe em vários sistemas, as alterações devem ser coordenadas em todas as cópias simultaneamente. Consolidar essa duplicação reduz a sobrecarga de coordenação e evita divergências.
Prioridade 4: Remoção de código morto. Código morto é a categoria mais segura para se tratar: removê-lo não quebra o comportamento, apenas revela dependências previamente ocultas. Ele deve ser removido antes de qualquer migração ou conversão para evitar desperdício de esforço convertendo código que nunca será chamado.
Prioridade 5: Problemas de estilo e estrutura em áreas de baixo risco. Métodos longos e listas de parâmetros extensas em código estável e com baixa taxa de alteração podem ser tratados de forma oportunista, quando o código próximo precisar ser alterado por outros motivos; refatore os problemas adjacentes ao mesmo tempo.
A disciplina de detectar, medir e corrigir problemas de código de forma sistemática, em vez de reativa, quando um problema já causou uma falha em produção, é o que distingue as equipes de desenvolvimento que mantêm a velocidade de entrega ao longo do tempo daquelas que diminuem progressivamente o ritmo à medida que seus sistemas crescem.