Pular para o conteúdo

Clean Code

Robert C. Martin (“Clean Code”, 2008) parte de uma constatação econômica: lê-se código muito mais do que se escreve — a proporção passa de 10 para 1. Portanto, otimizar para leitura é otimizar para produtividade. Kent Beck resume o mesmo espírito nas Rules of Simple Design: o código deve passar nos testes, revelar intenção, não ter duplicação e ter o mínimo de elementos.

Código limpo não é o mais curto nem o mais esperto; é o que o próximo leitor entende sem esforço e altera sem medo. É a meta da tríade descrita em Clean Code e Refactoring.

Um nome deve responder por que existe, o que faz e como é usado, dispensando comentário. Evite ruído (data, info, manager), notações húngaras e abreviações crípticas. Nomes de classe são substantivos; nomes de método são verbos. Use nomes pronunciáveis e buscáveis.

int d;
List<int[]> list1;
public List<int[]> getThem() {
List<int[]> list1 = new ArrayList<>();
for (int[] x : theList)
if (x[0] == 4)
list1.add(x);
return list1;
}

Depois, o mesmo código revela a intenção sem uma linha de comentário:

int elapsedTimeInDays;
List<Cell> flaggedCells;
public List<Cell> getFlaggedCells() {
List<Cell> flaggedCells = new ArrayList<>();
for (Cell cell : gameBoard)
if (cell.isFlagged())
flaggedCells.add(cell);
return flaggedCells;
}

Funções devem ser pequenas — e depois menores ainda. A heurística mais forte de Martin é um nível de abstração por função: uma função ou orquestra chamadas de alto nível, ou executa detalhes de baixo nível, nunca ambos. Misturar níveis (chamar calcularImposto() e, na linha seguinte, concatenar strings de HTML) força o leitor a subir e descer degraus mentais.

Antes: função longa, múltiplos níveis, efeitos misturados

Seção intitulada “Antes: função longa, múltiplos níveis, efeitos misturados”
public String gerarReciboPedido(Pedido pedido) {
double subtotal = 0;
for (Item item : pedido.getItens()) {
subtotal += item.getPreco() * item.getQuantidade();
}
double imposto = subtotal * 0.18;
double desconto = 0;
if (pedido.getCliente().getCategoria().equals("VIP")) {
desconto = subtotal * 0.10;
}
double total = subtotal + imposto - desconto;
StringBuilder sb = new StringBuilder();
sb.append("RECIBO\n");
sb.append("Subtotal: ").append(subtotal).append("\n");
sb.append("Imposto: ").append(imposto).append("\n");
sb.append("Desconto: ").append(desconto).append("\n");
sb.append("Total: ").append(total).append("\n");
return sb.toString();
}

Depois: cada função em um único nível de abstração

Seção intitulada “Depois: cada função em um único nível de abstração”
private static final double ALIQUOTA_IMPOSTO = 0.18;
private static final double TAXA_DESCONTO_VIP = 0.10;
public String gerarReciboPedido(Pedido pedido) {
double subtotal = calcularSubtotal(pedido);
double imposto = calcularImposto(subtotal);
double desconto = calcularDesconto(pedido, subtotal);
double total = subtotal + imposto - desconto;
return formatarRecibo(subtotal, imposto, desconto, total);
}
private double calcularSubtotal(Pedido pedido) {
return pedido.getItens().stream()
.mapToDouble(item -> item.getPreco() * item.getQuantidade())
.sum();
}
private double calcularImposto(double subtotal) {
return subtotal * ALIQUOTA_IMPOSTO;
}
private double calcularDesconto(Pedido pedido, double subtotal) {
return pedido.getCliente().isVip() ? subtotal * TAXA_DESCONTO_VIP : 0;
}
private String formatarRecibo(double subtotal, double imposto,
double desconto, double total) {
return new StringBuilder()
.append("RECIBO\n")
.append("Subtotal: ").append(subtotal).append("\n")
.append("Imposto: ").append(imposto).append("\n")
.append("Desconto: ").append(desconto).append("\n")
.append("Total: ").append(total).append("\n")
.toString();
}

A função de topo agora lê-se como um parágrafo em prosa. Cada extração é uma aplicação de Extract Function.

Martin trata o comentário como um mal necessário: quando você sente vontade de escrever um, primeiro tente melhorar o código para que ele seja desnecessário. Comentários que compensam código ruim (explicar um nome péssimo, resumir um bloco que deveria ser um método) mentem com o tempo, porque não são compilados nem testados e derivam do código.

if (funcionario.getCargaHoraria() > 40
&& funcionario.getTipo().equals("CLT")) {
}
if (funcionario.temDireitoAHoraExtra()) {
}

Comentários legítimos: explicações de intenção não óbvia, avisos de consequência, TODOs rastreados, Javadoc de APIs públicas. Neste material não escrevemos comentários no código de exemplo justamente para forçar a expressividade dos nomes.

Erros não devem obscurecer a lógica. Martin recomenda exceções em vez de códigos de retorno, não retornar nem passar null, e extrair o corpo do try/catch para funções próprias, pois “tratar erro é uma coisa só”.

public void processar() {
try {
Registro r = repositorio.buscar(id);
if (r != null) {
r.aplicar();
}
} catch (SQLException e) {
logger.error("erro", e);
}
}
public void processar() {
try {
buscarEaplicar(id);
} catch (RegistroException e) {
logger.error("falha ao processar registro {}", id, e);
}
}
private void buscarEaplicar(long id) {
Registro registro = repositorio.buscar(id);
registro.aplicar();
}

Repare que a limpeza aqui incluiu mudar o contrato: buscar deixou de poder retornar null e passou a lançar RegistroException (exceção de domínio no lugar da SQLException técnica). Isso não é um refactoring comportamentalmente neutro no sentido de Fundamentos de Refactoring — é um redesign deliberado do tratamento de erro, que exige atualizar o repositório e seus chamadores em conjunto.

“Deixe o acampamento mais limpo do que você o encontrou.”

Aplicada ao código: a cada commit, melhore algo pequeno — renomeie uma variável, extraia um método, elimine uma duplicação. A limpeza deixa de ser um projeto épico e passa a ser um hábito incremental. É a manifestação prática do refactoring contínuo defendido em Fundamentos de Refactoring.

O princípio de coesão que atravessa Clean Code — funções que fazem uma coisa, classes com uma responsabilidade — é a mesma ideia formalizada em SRP - Single Responsibility Principle. “Fazer uma coisa” no nível de função e “ter uma única razão para mudar” no nível de classe são a mesma força de projeto operando em escalas diferentes.

  • Nomes que enganamgetList() que faz I/O, isValid() que muda estado.
  • Funções-Deus — centenas de linhas, múltiplos níveis de abstração, flags booleanas de controle.
  • Argumentos demais — mais de três parâmetros geralmente esconde um objeto ausente (Introduce Parameter Object).
  • Comentários como desculpa — comentar código ruim em vez de reescrevê-lo.
  • Dogmatismo — tratar heurísticas (“4 linhas por função”) como leis absolutas; contexto manda.

1. O que significa “um nível de abstração por função” e por que importa?

Resposta

Significa que uma função ou coordena chamadas de alto nível ou executa detalhes de baixo nível, nunca as duas coisas na mesma função. Importa porque misturar níveis obriga o leitor a alternar constantemente entre o “o quê” e o “como”, aumentando a carga cognitiva. A cura é extrair os detalhes para funções nomeadas.

2. Por que Robert Martin trata comentários como um “fracasso”?

Resposta

Porque um comentário costuma ser a admissão de que o código não conseguiu se expressar sozinho. Comentários não são compilados nem testados, então divergem do código com o tempo e passam a mentir. Sempre que possível, transforme a intenção do comentário em um nome de método ou variável.

3. O que é a Boy Scout Rule e como ela se relaciona com refactoring?

Resposta

“Deixe o código mais limpo do que o encontrou.” É a prática de fazer pequenas melhorias a cada passagem pelo código, transformando limpeza num hábito incremental. É a face cotidiana do refactoring contínuo, oposta ao “sprint de refactoring” isolado.

4. Qual a ligação entre Clean Code e o princípio SRP?

Resposta

“Funções fazem uma coisa” e “classes têm uma única razão para mudar” são a mesma força de coesão em escalas diferentes. A disciplina de nomes e funções pequenas do Clean Code é a versão de baixo nível do SRP formalizado em SOLID.