Pular para o conteúdo

Code Smells

O termo é de Kent Beck, popularizado no capítulo “Bad Smells in Code” de Fowler (“Refactoring”, 1ª ed., 1999; revisado na 2ª ed., 2018 — algumas notas usam os nomes clássicos da 1ª ed., como Long Method e Switch Statements, que a 2ª renomeou para Long Function e Repeated Switches). O agrupamento em famílias abaixo (Bloaters, Couplers etc.) é a classificação didática de Mäntylä, popularizada pelo Refactoring.Guru — não é a organização do capítulo de Fowler. Um smell é uma característica do código que, por experiência, costuma indicar um problema de design mais fundo. Ele não prova o problema — apenas sugere onde cheirar. A cura é sempre uma ou mais Técnicas de Refactoring, e a meta é reaproximar-se de Clean Code.

mindmap
  root((Code Smells))
    Bloaters
      Long Method
      Large Class
      Long Parameter List
      Primitive Obsession
    OO Abusers
      Switch Statements
      Refused Bequest
    Change Preventers
      Divergent Change
      Shotgun Surgery
    Dispensables
      Duplicated Code
      Dead Code
      Comments
    Couplers
      Feature Envy
      Inappropriate Intimacy
      Message Chains

Bloaters — código, métodos e classes que incharam

Seção intitulada “Bloaters — código, métodos e classes que incharam”

Estruturas que cresceram além do gerenciável, geralmente por acúmulo gradual.

Smell Sintoma Refactoring que costuma curar
Long Method Método longo, muitos níveis de abstração, difícil de nomear inteiramente Extract Function, Replace Temp with Query
Large Class Classe com campos e responsabilidades demais Extract Class — viola SRP
Long Parameter List 4+ parâmetros, alguns sempre juntos Introduce Parameter Object
Primitive Obsession Uso de String/int para conceitos de domínio (CPF, dinheiro, e-mail) Replace Primitive with Object → Value Objects
public void transferir(String contaOrigem, String contaDestino, long valorEmCentavos) {
if (valorEmCentavos <= 0) throw new IllegalArgumentException();
}
public void transferir(NumeroConta origem, NumeroConta destino, Dinheiro valor) {
valor.exigirPositivo();
}

O tipo Dinheiro (um Value Object) concentra validação e semântica que, com primitivos, ficariam espalhadas por todo lugar.

Object-Orientation Abusers — OO aplicado de forma incompleta

Seção intitulada “Object-Orientation Abusers — OO aplicado de forma incompleta”
Smell Sintoma Refactoring que costuma curar
Switch Statements switch/if-else sobre um tipo que se repete pelo código Replace Conditional with Polymorphism → habilita OCP
Refused Bequest Subclasse herda mas ignora ou rejeita métodos/campos da superclasse Push Down, Replace Inheritance with Delegation — viola LSP
public double calcularArea(Forma f) {
switch (f.getTipo()) {
case CIRCULO: return Math.PI * f.getRaio() * f.getRaio();
case QUADRADO: return f.getLado() * f.getLado();
default: throw new IllegalArgumentException();
}
}

Cada nova forma força editar este switch (e todos os outros iguais). A cura polimórfica está em Replace Conditional with Polymorphism.

Estruturas que fazem uma alteração simples exigir esforço desproporcional.

Smell Sintoma Refactoring que costuma curar
Divergent Change Uma classe muda por muitas razões diferentes Extract Class / Split Phase — viola SRP
Shotgun Surgery Uma mudança exige editar muitas classes espalhadas Move Method/Field, Inline Class — viola OCP

Divergent Change e Shotgun Surgery são espelhos: o primeiro é “uma classe, muitas razões de mudança”; o segundo é “uma razão de mudança, muitas classes”. Ambos indicam responsabilidades mal alocadas.

Smell Sintoma Refactoring que costuma curar
Duplicated Code Mesma estrutura em vários lugares Extract Function, Pull Up Method — o smell nº 1 a caçar
Dead Code Código nunca executado (variável, parâmetro, método, branch) Remove Dead Code, Inline
Comments Comentários que compensam código ruim Extract Function, Rename — ver Clean Code
Smell Sintoma Refactoring que costuma curar
Feature Envy Método interessado mais nos dados de outra classe que na própria Move Method, Extract Function
Inappropriate Intimacy Duas classes conhecem detalhes internos uma da outra Move Method/Field, Hide Delegate
Message Chains a.getB().getC().getD().fazer() Hide Delegate, Extract Function (Lei de Deméter)
public class Fatura {
public double desconto(Cliente cliente) {
if (cliente.getPontos() > 1000
&& cliente.getCategoria().equals("OURO")) {
return 0.15;
}
return 0;
}
}

O método desconto usa só dados de Cliente — inveja as features dele. A cura é mover a lógica para Cliente.percentualDeDesconto().

Ponto-chave: smells são violações de SOLID vistas do código

Seção intitulada “Ponto-chave: smells são violações de SOLID vistas do código”

O mapeamento não é acidental — smells e princípios descrevem a mesma tensão de design por ângulos diferentes:

flowchart LR
    S1[Shotgun Surgery] --> P1[OCP]
    S2[Large Class / Divergent Change] --> P2[SRP]
    S3[Refused Bequest] --> P3[LSP]
    S4[Switch Statements] --> P1

O mapa completo está em Code Smells e SOLID. Reconhecer o smell te dá o gatilho; conhecer o princípio te diz a direção da cura.

  • Tratar smell como bug — smell é indício, não defeito; às vezes o design atual é o certo para o contexto.
  • Caçar smells sem testes — remover um smell sem rede de segurança arrisca comportamento (ver Fundamentos de Refactoring).
  • Ignorar Duplicated Code — é o smell mais barato de detectar e o de maior retorno ao remover.
  • Confundir Comments legítimos com o smell Comments — o smell é o comentário que existe para desculpar código ruim, não a documentação de intenção genuína.
  • Purismo — remover todo smell cegamente pode superengenheirar; pese o custo da mudança.

1. O que distingue um code smell de um bug?

Resposta

Um bug é comportamento incorreto; um smell é uma característica estrutural que, por experiência, indica um problema de design mais profundo. O smell não prova nada — só sugere onde investigar. Às vezes o design “com cheiro” é o adequado para o contexto.

2. Qual a diferença entre Divergent Change e Shotgun Surgery?

Resposta

São espelhos. Divergent Change: uma classe muda por muitas razões diferentes (viola SRP). Shotgun Surgery: uma única razão de mudança obriga editar muitas classes espalhadas (viola OCP). Ambos apontam responsabilidades mal distribuídas.

3. Como Primitive Obsession se relaciona com Value Objects?

Resposta

Primitive Obsession é usar tipos primitivos (String, int) para representar conceitos de domínio como CPF, dinheiro ou e-mail. A cura é Replace Primitive with Object, introduzindo um Value Object que concentra validação e semântica antes espalhadas. Ver Value Objects.

4. Por que se diz que muitos smells são violações de SOLID?

Resposta

Porque smells e princípios descrevem a mesma tensão de design por ângulos diferentes: Large Class/Divergent Change ↔ SRP, Shotgun Surgery e Switch Statements ↔ OCP, Refused Bequest ↔ LSP. O smell é o gatilho observável; o princípio indica a direção da cura. Ver Code Smells e SOLID.