W3docs

Princípios de Código Limpo em Java

Princípios de código limpo em Java: métodos pequenos, nomes significativos, responsabilidade única e mutação mínima.

Código limpo é aquele que a próxima pessoa — muitas vezes você mesmo, seis meses depois — consegue ler, confiar e modificar sem medo. O compilador aceita quase tudo; código limpo é sobre o outro público, os humanos que o mantêm. Nenhum dos princípios abaixo é uma invenção específica do Java, mas o Java oferece ferramentas concretas — métodos pequenos, campos final, records, exceções, tipos significativos — para aplicá-los. Este capítulo percorre os que trazem resultados todos os dias.

Nomes que revelam intenção

Um bom nome responde por que o valor existe, não apenas qual é o tipo. Se um nome precisa de um comentário para ser explicado, o nome está errado. Evite letras isoladas (exceto em laços curtos), evite abreviações que só você entende e prefira um nome descritivo mais longo a um nome curto e enigmático — sua IDE completa automaticamente de qualquer forma.

// Unclear: what is d? what unit? what is the magic 86400000?
int d = (t2 - t1) / 86400000;

// Clear: the names and a constant carry the meaning
long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;
long elapsedDays = (endMillis - startMillis) / MILLIS_PER_DAY;

Booleanos ficam melhores como perguntas ou estados: isActive, hasNext, shouldRetry. Métodos que fazem algo recebem nomes de verbos (calculateTotal); métodos que retornam algo recebem nomes de substantivos ou getters (total, getTotal). Para as convenções da linguagem por trás dessas escolhas, veja convenções de nomenclatura Java e boas práticas de nomenclatura Java.

Métodos pequenos com responsabilidade única

Um método deve fazer uma coisa em um único nível de abstração. Quando você se pega adicionando um comentário como // now validate the input, esse bloco geralmente quer se tornar seu próprio método bem nomeado. Métodos curtos são mais fáceis de nomear, testar e reutilizar — e o local de chamada lê como uma frase.

// Before: one method juggling validation, calculation, and formatting
String receipt(Order order) {
  if (order == null || order.items().isEmpty())
    throw new IllegalArgumentException("empty order");
  int total = 0;
  for (var i : order.items()) total += i.price() * i.qty();
  return "Total: $" + total / 100 + "." + (total % 100);
}

// After: each step is a named method; receipt() now reads top-down
String receipt(Order order) {
  requireNonEmpty(order);
  int total = totalCents(order);
  return formatCents(total);
}

Cláusulas de guarda mantêm os métodos planos. Trate os casos extremos e retorne cedo em vez de envolver o caminho feliz em blocos if cada vez mais profundos.

Prefira imutabilidade e mutação mínima

Estado mutável compartilhado é a raiz da maioria dos bugs de concorrência e de muita confusão comum. Padronize com campos e variáveis locais final; só relaxe para mutável quando tiver um motivo. Os records do Java tornam objetos de valor imutáveis uma linha só, gerando um construtor canônico, equals, hashCode e toString.

// An immutable value object with validation in the compact constructor
record Money(long cents, String currency) {
  Money {
    if (cents < 0) throw new IllegalArgumentException("cents must be >= 0");
  }
  Money plus(Money other) { return new Money(cents + other.cents, currency); }
}

Observe que plus retorna um novo Money em vez de mutar this. Objetos imutáveis são seguros para compartilhar entre threads, seguros para usar como chaves de mapa e impossíveis de corromper após a construção. Para se aprofundar, veja Java records, classes imutáveis e boas práticas de imutabilidade; a palavra-chave final cobre como bloquear campos e variáveis.

Falhe rápido e use exceções, não códigos de erro

Valide argumentos na fronteira e lance imediatamente quando algo estiver errado, para que a falha apareça perto de sua causa em vez de três camadas abaixo como uma confusa NullPointerException. Use exceções para sinalizar erros; não retorne null ou valores sentinela mágicos que todo chamador deve se lembrar de verificar.

PadrãoEvitarPreferir
Valor ausentereturn null;Optional<T> ou lançar exceção
Argumento inválidoreturn -1;throw new IllegalArgumentException(...)
Estado impossívelpadrão silenciosothrow new IllegalStateException(...)
Limpeza de recursosfinally manualtry-with-resources
static User findUser(String id) {
  Objects.requireNonNull(id, "id must not be null");   // fail fast
  return repository.lookup(id)
      .orElseThrow(() -> new NoSuchElementException("no user: " + id));
}

Objects.requireNonNull transforma um NPE vago downstream em uma mensagem precisa no ponto de entrada. Para modelar "pode estar ausente" sem null, leia Java Optional; para projetar tipos de erro, veja boas práticas de exceções e exceções personalizadas.

DRY, mas sem over-abstrair

DRY (Don't Repeat Yourself) significa que uma única peça de conhecimento vive em um lugar. Quando a mesma constante ou cálculo aparece duas vezes, extraia-o. Mas resista ao erro oposto: dois trechos que apenas parecem iguais hoje podem divergir amanhã. Código duplicado é mais barato de corrigir do que a abstração errada. Extraia quando o significado é compartilhado, não apenas a sintaxe.

// Knowledge duplicated: the threshold lives in two places
if (order.total() >= 5000) freeShip = true;     // here
if (cart.total() >= 5000) showBadge = true;     // and here

// One source of truth
static final int FREE_SHIPPING_THRESHOLD_CENTS = 5000;
boolean qualifiesForFreeShipping(int totalCents) {
  return totalCents >= FREE_SHIPPING_THRESHOLD_CENTS;
}

Um exemplo prático: um carrinho de compras limpo

Este programa reúne os princípios em um arquivo pequeno e executável: um record LineItem imutável que se valida, métodos de propósito único com nomes que revelam intenção, constantes nomeadas em vez de números mágicos, uma cláusula de guarda e igualdade baseada em valor. Nenhum comentário é necessário para explicar o que faz — os nomes fazem isso.

java— editable, runs on the server

O que tirar da execução:

  • A saída lê exatamente como o domínio que modela — 2 x Notebook @ $12.50 = $25.00 — porque todo método e campo é nomeado conforme seu propósito. Você não precisou de um único comentário para acompanhar o cálculo do carrinho; nomes que revelam intenção fizeram a documentação.
  • O subtotal é $30.97 e o frete é $5.99, não gratuito: a cláusula de guarda em shippingCents comparou o subtotal com a constante nomeada FREE_SHIPPING_THRESHOLD_CENTS (5000), e 3097 está abaixo disso. O número mágico vive em exatamente um lugar, então a regra é impossível de ficar inconsistente.
  • value equality: true prova que o record nos deu equals por valor gratuitamente — dois itens Pen construídos separadamente são iguais porque seus dados são iguais. Escrever esse par equals/hashCode manualmente é boilerplate que você não precisa mais manter.
  • rejected bad item: quantity must be >= 0 mostra o fail-fast em ação: o construtor compacto validou o argumento e lançou IllegalArgumentException no momento da construção, para que uma quantidade -1 nunca entre no sistema como dado inválido silencioso.
  • Cada auxiliar — subtotalCents, shippingCents, formatCents — faz uma coisa, então main lê de cima para baixo como uma história. Métodos pequenos de responsabilidade única são o que tornam o programa inteiro legível em vez de uma parede de lógica aninhada.

Prática

Prática
Por que tornar 'LineItem' um record com validação em seu construtor compacto é considerado mais limpo do que uma classe mutável comum com setters?
Por que tornar 'LineItem' um record com validação em seu construtor compacto é considerado mais limpo do que uma classe mutável comum com setters?
Was this page helpful?