Pular para o conteúdo

Boas práticas e padrões · Clean Code · 6 min de leitura

Code smells e refatoração

Sinais de que o design está piorando e as refatorações seguras que os corrigem — sempre em passos pequenos com testes verdes.

Cheiro → Testes verdes1Testes verdes → Passo pequeno2Passo pequeno → Rodar testes3Rodar testes → Cheiro: próximopróximo4Cheiro — método longo…Cheirométodo longo…Testes verdes — rede de segurançaTestes verdesrede de segurançaPasso pequeno — extrair, renomearPasso pequenoextrair, renomearRodar testes — ainda verde?Rodar testesainda verde?

Arraste para o lado para ver o diagrama inteiro.

Refatorar é um ciclo curto: nunca mude comportamento e estrutura ao mesmo tempo, e rode os testes a cada passo.

Cheiros mais comuns

  • Método longo e classe grande.
  • Lista longa de parâmetros → agrupe num objeto.
  • Obsessão por primitivos: CPF, dinheiro e e-mail como String/Double → crie tipos de valor.
  • Inveja de funcionalidade: método que usa mais dados de outra classe que da própria.
  • Cirurgia com espingarda: uma mudança exige editar dez arquivos.
  • Switch repetido sobre o mesmo tipo → polimorfismo.

Refatorações de base

Do catálogo de Fowler: Extrair função, Renomear, Introduzir objeto de parâmetro, Substituir condicional por polimorfismo, Mover função. As IDEs (IntelliJ, Android Studio, VS Code) fazem várias com segurança.

Regra do escoteiro

Deixe o código um pouco melhor do que encontrou — em commits separados das mudanças de comportamento, para a revisão ficar fácil.

No código: antes e depois

✗ AntesKotlin
fun transferir(deCpf: String, paraCpf: String, valor: Double, moeda: String, agendada: Boolean, data: String?) {
  if (deCpf.length != 11) throw IllegalArgumentException()
  if (valor <= 0) throw IllegalArgumentException()
  // ...
}
✓ DepoisKotlin
@JvmInline value class Cpf(val digitos: String) {
  init { require(digitos.length == 11 && digitos.all(Char::isDigit)) { "CPF inválido" } }
}
data class Dinheiro(val centavos: Long, val moeda: Currency) {
  init { require(centavos > 0) { "valor deve ser positivo" } }
}
data class Transferencia(val de: Cpf, val para: Cpf, val valor: Dinheiro, val agendadaPara: LocalDate? = null)

fun transferir(t: Transferencia) { /* a validação já aconteceu nos tipos */ }

Obsessão por primitivos e lista longa de parâmetros resolvidas: um CPF inválido nem chega a existir como valor.

Em resumo

Referências

Continue aprendendo

Todos os temas de boas práticas e padrões →