W3docs

Boas Práticas de Tratamento de Exceções em Java

Regras práticas para tratamento de exceções em Java: falhe rápido, lance o tipo certo, nunca engula exceções e registre com utilidade.

Os capítulos anteriores cobriram a mecânica — try, catch, finally, throw, throws, classes personalizadas. Este é o lado do julgamento. Dois programas podem usar os mesmos construtos e um ser robusto enquanto o outro é frágil. A diferença está em um pequeno conjunto de hábitos que se tornam reflexo com a prática.

Falhe rápido em entradas inválidas

Se um método for chamado com argumentos que ele não consegue processar, lance imediatamente:

public void send(String to, String body) {
  if (to == null || to.isBlank()) {
    throw new IllegalArgumentException("to must be non-blank");
  }
  if (body == null) {
    throw new NullPointerException("body");
  }
  // ...real work
}

Não tente "fazer o melhor possível" com null. O bug está no chamador, e quanto mais próxima a exceção cair dele, mais fácil será corrigi-lo. Objects.requireNonNull(body, "body") é o one-liner padrão para o caso de null.

O hábito oposto — substituir silenciosamente entradas ausentes por valores padrão — leva a erros que surgem cinco camadas adiante sem nenhuma pista sobre quem passou o quê.

Lance o tipo mais específico que se encaixar

Exception raramente é a coisa certa a lançar, e RuntimeException apenas quando nenhum tipo mais específico se encaixa. A biblioteca padrão fornece um vocabulário — use-o:

  • Argumento inválido → IllegalArgumentException
  • Argumento null → NullPointerException (Objects.requireNonNull)
  • Estado incorreto → IllegalStateException
  • Operação não suportada → UnsupportedOperationException
  • Índice fora do intervalo → IndexOutOfBoundsException
  • Número fora do intervalo → ArithmeticException ou IllegalArgumentException

Para falhas de domínio, escreva uma exceção personalizada em vez de reutilizar uma built-in.

Capture o tipo mais específico para o qual você tem um plano

A regra simétrica. catch (Exception e) é um sinal de alerta. Ela captura bugs de programação (NullPointerException, IllegalStateException) e falhas recuperáveis (IOException) e exceções de biblioteca desconhecidas tudo em um único balde — e quase sempre as trata de forma idêntica, o que quase sempre está errado.

// Bad — what does this even handle?
try { complex(); }
catch (Exception e) { log("failed"); }

// Better — specific cases get specific responses
try { complex(); }
catch (IOException e)         { retryLater(); }
catch (ParseException e)      { recordCorruptInput(e); }

Quando você genuinamente não sabe o que fazer com uma classe de exceção, a resposta é não a capture. Deixe-a propagar para um handler que saiba.

Nunca engula uma exceção silenciosamente

O pior padrão único de tratamento de exceções:

try { doWork(); }
catch (Exception e) { }    // never write this

Quando o inevitável bug de produção ocorrer, não há stack trace, nenhuma mensagem, nenhuma entrada de log — a falha simplesmente desapareceu. Se você genuinamente pretende ignorar uma falha (raro, mas possível — por exemplo, fechar um recurso em um caminho de limpeza), diga isso explicitamente:

try { connection.close(); }
catch (IOException ignored) {
  // close-time failure on a cleanup path; original cause already propagating
}

O nome de variável ignored e o comentário tornam a intenção visível para o próximo leitor.

Registre de forma útil, registre uma vez

Dois problemas de logging são comuns:

  • Registrar sem contexto suficientelog.error("failed") não diz nada.
  • Registrar e depois relançar — cada camada registra a mesma exceção, e o mesmo trace acaba no log cinco vezes.

Escolha uma camada que conheça o máximo de contexto (geralmente de alto nível: request handler, job runner) e registre lá com o input que desencadeou a falha. As camadas abaixo devem se concentrar em traduzir a exceção, não em registrá-la.

try {
  userService.activate(id);
} catch (UserNotFoundException e) {
  log.warn("activation failed: no user with id={}", id, e);   // include the exception object as the last arg
  return Response.notFound();
}

Passar a exceção como o último argumento do logger é a convenção SLF4J — ela garante que o stack trace completo e qualquer cadeia de causa apareçam na saída.

Preserve a causa ao encapsular

Ao traduzir uma exceção para uma camada superior, sempre passe o original como a causa:

// Good — cause is preserved
catch (IOException e) {
  throw new ConfigLoadException("failed to load " + path, e);
}

// Bad — original IOException is lost
catch (IOException e) {
  throw new ConfigLoadException("failed to load " + path);
}

A cadeia Caused by: no stack trace resultante é o que permite ao engenheiro de plantão rastrear uma exceção de domínio de volta à falha no nível de bytes. Perca-a e uma sessão de depuração de meia hora se torna um meio dia.

Não use exceções para controle de fluxo

Lançar é caro — construir um stack trace na criação é o maior custo. Mais importante, isso obscurece a intenção. Um loop que usa try/catch (NoSuchElementException) para saber quando parar está escondendo o que está fazendo:

// Bad
try {
  while (true) {
    process(iter.next());
  }
} catch (NoSuchElementException end) { }

// Good
while (iter.hasNext()) {
  process(iter.next());
}

Quando "não encontrado" é um resultado comum, retorne Optional<T> ou um boolean. Reserve exceções para o verdadeiramente excepcional.

Use finally e try-with-resources para limpeza

finally deve liberar recursos. try-with-resources deve ser o padrão para qualquer coisa AutoCloseable. Não coloque lógica de negócio em finally — ela é executada em ambos os caminhos de sucesso e falha e não pode diferenciá-los. E não use return em finally — ele descarta silenciosamente a exceção original ou o valor de retorno, o que é um dos bugs mais difíceis de diagnosticar.

Documente o que você lança

Se um método pode lançar uma exceção que importa aos chamadores — verificada ou não verificada — diga no Javadoc:

/**
 * Looks up a user by id.
 *
 * @throws UserNotFoundException if no user with that id exists
 * @throws IllegalArgumentException if id is null or blank
 */
public User lookup(String id) { ... }

O compilador aplica isso para exceções verificadas na assinatura. Para as não verificadas, o Javadoc é o único contrato — e os chamadores realmente precisam dele quando a exceção afeta como devem usar o método.

Use exceções para falhas, retornos para resultados rotineiros

A regra resumida, e a que une todas as demais. Uma exceção diz algo deu errado que não consigo corrigir aqui. Um valor de retorno diz aqui está o resultado. Quando "não encontrado" faz parte da operação normal, retorne Optional.empty(), um boolean ou um sentinel. Quando "a conexão com o banco de dados caiu" acontecer, lance.

Código que observa essa distinção é tranquilo: o caminho feliz parece uma linha reta, o caminho incomum está em um bloco diferente, e o leitor consegue dizer de relance qual é qual.

Um exemplo trabalhado

Uma pequena função de processamento de pedidos que reúne as práticas deste capítulo — validação fail-fast, tipos de exceção built-in específicos, encapsulamento com causa e um único handler de nível superior que registra uma vez.

java— editable, runs on the server

Quatro chamadas, quatro caminhos diferentes. A bem-sucedida retorna normalmente. Os dois casos de IllegalArgumentException (id null, amount zero) são reportados com a mensagem que explica o que estava errado com o input. A falha de serviço simulada aparece como um OrderProcessingException de domínio com o IllegalStateException original vinculado por meio de getCause(). Nada é engolido, nada é registrado duas vezes e cada falha indica exatamente qual valor a causou.

O que vem a seguir

Isso encerra a Parte 8 — você tem domínio funcional da maquinaria de exceções do Java e o julgamento para usá-la bem. A próxima parte faz um tour aprofundado pelas strings — o tipo mais comum no código Java por uma grande margem e um com mais profundidade do que sua superfície sugere. Continue para Java String class.

Prática

Prática
Qual desses hábitos de tratamento de exceções tem **mais** probabilidade de tornar a depuração em produção dolorosa?
Qual desses hábitos de tratamento de exceções tem **mais** probabilidade de tornar a depuração em produção dolorosa?
Was this page helpful?