Lidando com Exceptions
Por Fabio Kung em 07/10/06Pequena 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.RuntimeExceptiondireta 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ãothrow 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 comothrow 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!
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.
Comment by Michael Nascimento Santos — October 9, 2006 @ 8:03 am
Muito bacana Fábio.
Abraço.
Rodrigo Soriano Arcova.
Comment by Rodrigo Soriano Arcova — October 13, 2006 @ 4:59 pm
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.
Comment by Paulo Silveira — October 17, 2006 @ 4:40 pm
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.
Comment by Vinícius — December 12, 2006 @ 5:20 pm
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.
Comment by Antonio — May 28, 2007 @ 2:41 pm
Um artigo muito bom que vale a leitura sobre tratamento de exceções:
http://tutorials.jenkov.com/java-exception-handling/index.html
Comment by Daniel Destro — July 3, 2009 @ 7:46 am
[...] erro aqui é a péssima prática de engolir exceptions, que costuma aparecer em diversos códigos por aí. Ao debugar rapidamente, iremos perceber que [...]
Pingback by Logar é preciso, debugar não é preciso? | blog.caelum.com.br — May 27, 2010 @ 12:55 am