Lidando com Exceptions

Pequena Revisão
Sabemos que as exceções em Java são classificadas em dois tipos:

  • Checked Exceptions: seguem a regra do handle-or-declare. O desenvolvedor é obrigado a tratar (try-catch) ou relançar (throws), caso não saiba como tratar.
  • Unchecked Exceptions: não é obrigatório tratar nem relançar, apesar de ser possível. Caso não haja try-catch adequado à exceção gerada, ela é automaticamente relançada. Geralmente são filhas de java.lang.RuntimeException direta ou indiretamente.

Uma das grandes dificuldades dos desenvolvedores Java é saber como lidar adequadamente com exceções. Perguntas como “Minha exceção deve ser checked ou unchecked?” e “O que escrever no meu bloco catch?” são extremamente comuns. Espero esclarecer um pouco estas dúvidas expondo aqui a minha maneira de agir nestes casos.

Gerando exceções
Aconteceu algo de errado no seu sistema (atingiu um estado inconsistente) e você está louco para fazer throw new Exception(); ?

  • Primeira regra: coloque no mínimo uma mensagem explicativa na sua exceção. Quantas vezes você não ficou nervoso com o seu colega desenvolvedor, que jogou uma exceção sem mensagem nenhuma e não há como ter a mínima idéia do que aconteceu? Melhorando um pouco fica: throw new Exception("Um dos argumentos para o cálculo do desconto é Inválido");
  • Segunda regra: use sempre exceções específicas ao seu caso, isto é, se o problema foi no cálculo de um desconto faça algo do tipo throw new CalculoDeDescontoException("Um dos argumentos é inválido"); e não throw new Exception("Não há como calcular o desconto pois um dos argumentos é inválido");. Aqui você poderia terminar com muitas exceções específicas demais. Como alternativa, poderia então fazer algo como throw new LogicaDeNegocioException("Um dos argumentos para o calculo do desconto é inválido"). Usando sempre o bom senso.

Checked ou Unchecked?
Pare um pouco para pensar no significado de um try-catch:

Uma chance de se recuperar do erro. Uma chance de tratar a exceção.

Tendo isso em mente fica mais fácil decidir entre lançar uma checked exception ou uma unchecked exception. Tente responder a seguinte pergunta:

Quero dar a chance a quem chama meu código de tratar o possível erro?

Se a resposta for sim, use uma checked exception. Neste caso o chamador vai ser obrigado a fazer o try-catch e terá a chance de se recuperar do erro. Não é o que você tanto quis?

Caso ele não saiba como se recuperar do erro, será obrigado a relançar a exceção. Assim, outro lugar terá a chance de se recuperar do erro. No fim das contas, o que uma checked exception trouxe foi a capacidade de recuperação.

Terceira regra: use checked exceptions para erros recuperáveis.

E quanto a erros irrecuperáveis? Nestes casos não queremos dar a chance de recuperação do erro. São casos como “queimou o banco de dados”, ou então NullPointerException, que é sempre culpa do programador (apesar do nosso orgulho dizer o contrário). Deu erro e pronto, não há o que fazer. A única saída é mostrar para o usuário do sistema que aconteceu um problema, uma mensagem do tipo:

Desculpe, ocorreu um erro interno. Contate o administrador.

Como para unchecked exceptions não é obrigatório o handle-or-declare o mais comum é que a exceção vá subindo na pilha de chamadas e seja exibida ao usuário. Aqui uma grande vantagem é que pode existir um componente especialista em lidar com errors irrecuperáveis. Ele poderia interceptar todas as ações do sistema e esperar por erros que ninguém tratou: erros irrecuperáveis. Quando um erro destes chegar, o componente poderia fazer log da ocorrência, fazer rollback da transação e formatar a mensagem adequada ao usuário (não parece muito legal jogar o Stacktrace na cara de um usuário, parece?)

Implementar este componente interceptador não é tão difícil atualmente:

  • Ambiente WEB: recursos como Servlet Filters que interceptam todas as requisições em um ambiente web ou tratamento de erro declarativo pertencente à especificação de servlets/jsp (seção error-page do web.xml).
  • Ambientes desktop: programação orientada a aspectos, proxies ou coisas menos avançadas como o padrão de projeto Decorator.

Quarta regra: use unchecked exceptions para erros irrecuperáveis.

Ei, a API que eu uso não segue estas regras!
Você, como bom samaritano, agora segue as boas práticas. Mas o JDBC por exemplo, não segue. Supondo que você seja obrigado a usá-lo:

try {
  PreparedStatement stmt = con.prepareStatement(query);
  // ...
catch (SQLException e) {
  // Epa! SQLException é erro grave, irrecuperável! Pode ter queimado o banco de dados...
  // Não deveria ser checked exception e, além disso, é genérica demais. 
  // Serve para muitos casos!

  throw new AcessoADadosException("Problema na criação do Statement",  e);
  // Agora sim, encapsulei a SQLException na minha exceção específica. 
  // Além disso ela é RuntimeException: unchecked!

}

Neste caso, AcessoADadosException é uma unchecked exception, já que o seu erro é irrecuperável e você não quer dar a chance de ninguém se recuperar dele. Além de que, agora a sua exceção é mais específica e descreve melhor o seu problema, sua API ficou mais limpa: não vão ser mais necessários try-catch(SQLException) nem throws SQLException espalhados pelo seu sistema todo.

De fato, o caso mais comum é o de erros irrecuperáveis. Por isso essa prática de transformar checked exceptions (mal empregadas) em unchecked exceptions têm se tornado comum.

Quinta regra: nunca faça:

catch(Exception e) {
    System.out.println(e);
}

Este código pega muito mais erros do que ele está realmente esperando e o pior, não está preparado para tratá-los. Sempre relance erros que você não está preparado para tratar. Nunca pegue um erro e ignore-o. O rollback de uma transação pode depender da ocorrência de um erro irrecuperável que você acaba de esconder do resto do sistema!

15 Comentários

  1. Michael Nascimento Santos 09/10/2006 at 08:03 #

    Outra regra interessante: se quiser apenas logar uma exceção e não etiver usando um framework de logging, faça:


    e.printStackTrace();

    e nunca:


    System.out.println(e);

    ou ainda:


    System.out.println(e.getMessage());

    Pelo menos a informação do stack trace será impressa e permitirá identificar o lugar que originou o erro.

  2. Rodrigo Soriano Arcova 13/10/2006 at 16:59 #

    Muito bacana Fábio.

    Abraço.

    Rodrigo Soriano Arcova.

  3. Paulo Silveira 17/10/2006 at 16:40 #

    Vale lembrar que


    e.printStackTrace();

    imprime no System.err

    Um exemplo clássico (e famoso) de mal uso de exceptions está no Velocity… tente pegar ou renderizar um template. O método joga 3 exceptions , algo como: ParserException, TemplateNotFoundException e Exception.

    Ahn!? Isso mesmo, Exception, você não sabe o que aconteceu dentro do método, só sabe que ocorreu uma exception.

  4. Vinícius 12/12/2006 at 17:20 #

    Ao invés de imprimir uma stacktrace no console o ideal é registrar um uncaughtexceptionhandler. Assim você pode encaminhar a exceção que não foi pega para um log.

    Você pode mudar o uncaughtExceptionHandler da Thread chamando: Thread.setUncaughtExceptionHandler
    ou mudar o da aplicação chamando o método
    Thread.setDefaultUncaughtExceptionHandler.

    Você terá que implementar a interface Thread.UncaughtExceptionHandler que define um método muito simples, que é chamado toda vez que uma runtime exception não é capturada. Nesse método sim, dê uma mensagem bem trabalhada para seu usuário e grave o feio stack trace da Thread em algum log.

  5. Antonio 28/05/2007 at 14:41 #

    Fiz o teste da fj31 e não entendi porque: 1) a questão 2 tem como resposta a opção c e não a opção a. 2) porque o código da questão 5 não compila e o código da questão 7 compila.

  6. Daniel Destro 03/07/2009 at 07:46 #

    Um artigo muito bom que vale a leitura sobre tratamento de exceções:
    http://tutorials.jenkov.com/java-exception-handling/index.html

  7. JuuGO(Programdor) 06/09/2011 at 14:22 #

    Otima a Postagem Explicativo e Pratico para os programadores que estão iniciando ou até mesmo programadores experientes.

  8. Marcelo Labbati 11/04/2012 at 17:42 #

    Gostei muito do tutorial. Quando vi a primeira vez este lance de unchekedException nao estava conseguindo abstrair este conceito de “Ter uma chance de tratar a excessão”. Agora ficou claro.
    Parabéns pelo post.

  9. Paulo 31/05/2012 at 15:05 #

    Boa Tarde …

    Para o caso da …
    throw new AcessoADadosException(“Problema na criação do Statement”, e);


    foi criada uma classe para isso?

    Obrigado

  10. Maicon Herverton 02/04/2013 at 11:21 #

    Prefiro:

    public static String getStack(Throwable exception) {
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    exception.printStackTrace(pw);
    return (sw.toString());
    }

  11. Eduardo 18/01/2017 at 10:04 #

    A minha dúvida em relação exception é a seguinte. Ao criar a minha própria classe para tratar uma exceção, por exemplo ValidationException:

    public class ValidationException extends Exception{

    private static final long serialVersionUID = 1L;

    public ValidationException(String msg) {
    super(msg);
    }

    public ValidationException(Exception e) {
    super(e.getMessage());
    }

    public ValidationException(String msg, Exception e) {
    super(msg, e);
    }
    }
    No construtor onde eu só recebo a Exception, se eu não chamar o e.getMessage, lá na minha página aparecerá o nome do pacote e classe junto com a mensagem de erro assim:

    br.edu.sistemaCadastro.exception.ValidationException: Campo Senha Obrigatório!
    Campo Usuário Obrigatório!

    Se puderem me explicar isso de uma maneira simples eu agradeço.

  12. Renato 09/09/2017 at 20:12 #

    #Eduardo terá que fazer esta mesma pergunta no fórum do guj pois é voltado especificamente para isso.

  13. Joel Mandrasse 25/05/2018 at 07:32 #

    E necessario mais um pouquinho de atencao Eduardo, vou fazer uma verificacao rapida do teu codigo.

  14. Eduardo 19/07/2018 at 14:32 #

    Artigo de 2006 e continua tão atual….

Deixe uma resposta