Exceções

Quando bem usadas, as exceções podem melhorar a legibilidade, confiabilidade e sustentabilidade do código. Mas quando usadas incorretamente, podem causar o efeito oposto. Este artigo fornece orientações sobre seu uso.

Item 39: Use exceções apenas para condições excepcionais
As exceções, como o próprio nome indica,
devem ser usadas apenas em condições excepcionais; nunca para controle de fluxo comum.
Seu uso costuma ter um impacto negativo na performance, uma vez que é custoso criar, lançar e pegar uma exceção, além de atrapalhar as otimizações feitas automaticamente pela JVM.
O uso de exceções para controle de fluxo também mascara o objetivo do código e aumenta a probabilidade de um bug passar desapercebido, ficando mais difícil encontrar a causa do erro posteriormente.
Lembre-se disso ao criar sua API, para não forçar o cliente da classe a usar exceções desnecessariamente. Uma classe que possui um método que depende de um certo estado normalmente deve incluir também um método separado para testar esse estado, indicando se é apropriado ou não chamar o primeiro método. Um exemplo desta técnica é a classe
Iterator, que possui os métodos next e hasNext.Outra alternativa é que o método que depende do estado retorne um valor especial, como null, quando chamado em um objeto em estado inapropriado. Esta segunda abordagem é a mais adequada para casos em que o estado do objeto pode mudar no intervalo entre a chamada do método teste e o método que depende do estado, ou que a performance seja crítica. Em outras situações, é melhor o método teste, pois é mais legível e é mais fácil detectar e corrigir um uso inapropriado.

Item 40: Use checked exceptions para condições  recuperáveis e runtime exceptions para erros de programação
Há três tipos de throwables na linguagem Java: checked exceptions, runtime exceptions e errors.
Utilize checked exceptions para condições em que se espera que a classe que chamou o método possa recuperar-se. O
lançamento da exceção é um indício ao usuário da API que aquela determinada condição é um dos retornos possíveis da chamada do método e assim você força que ele insira código para tratar a exceção ou propagá-la.
Existem dois tipos de unchecked throwables: runtime exceptions e errors. Eles são idênticos em seus comportamentos: ambos são throwables que não precisam ser tratados. Quando um programa lança algum deles, normalmente é um caso impossível de recuperar; continuar executando apenas pioraria a situação. Portanto, se o programa não incluir um bloco catch para tratar esta exceção, a thread em que ela acontece é interrompida mostrando uma mensagem de erro.
Utilize runtime exceptions para indicar erros de programação. Normalmente são usadas em violações de pré-condições, ou seja, quando o cliente não adere ao contrato estabelecido na API.
Já os erros são utilizados pela JVM para indicar uma deficiência de recursos, falhas ou outras condições que tornam impossível que a execução continue. Ainda que isso não seja determinado na especificação, é uma convenção bem aceita, portanto você não deve criar novas classes de erros; utilize subclasses de
RuntimeException para todos os unchecked throwables.
É possível definir throwables que não são subclasses de
Exception, RuntimeException ou Error. Seu comportamento fica igual ao de checked exceptions, portanto é melhor herdar da classe Exception, que é o mais comum.
Resumindo, se você acha que a situação provavelmente é recuperável, utilize checked exception, caso contrário, utilize runtime exception.
Além disso, ao criar novas classes de exceções, lembre-se de que é possível incluir métodos para retornar informações para o usuário, o que é uma prática muito melhor do que simplesmente obrigá-lo a obter os dados dentro da String que a representa. Isso é especialmente importante nas checked exceptions, para que a causa do problema seja identificada mais facilmente e contornada.

Item 41: Evite o uso desnecessário de checked exceptions
As checked exceptions tornam o código mais confiável ao forçar os programadores a lidar com condições excepcionais. Porém, seu uso excessivo faz com que fique mais desagradável trabalhar com a API, já que é necessário incluir código para tratar ou propagar cada exceção lançada.
Esse trabalho é justificável apenas se a condição excepcional não pode ser prevenida com um uso apropriado da API e o programador pode tomar alguma ação útil quando confrontado com esta exceção. Se o melhor que ele pode fazer é um
e.printStackTrace(),  melhor utilizar uma runtime exception.
O fardo é ainda maior quando é o caso de uma única checked exception lançada por um método. Quando já existem outras, a chamada ao método já estará dentro de um bloco
try. Então bastaria colocar um novo bloco catch. Mas quando uma exceção é sozinha responsável por obrigar a existência bloco try, deve ser considerado cuidadosamente se vale mesmo a pena incluí-la na assinatura do método.
Uma técnica para transformar uma checked exception em runtime exception é quebrar o método em duas partes, a primeira retornando um boleano que indica se a exceção seria lançada. Exemplo:
// Com checked exception
try {
    obj.actions(args);
} catch(TheCheckedException e) {
    // trata a condição
}

//Com teste de estado e runtime exception
if (obj.actionPermitted(args)) {
    obj.action(args);
} else {
    //trata a condição
}

Nem sempre esta transformação é apropriada, conforme discutido no item 39. Mas quando é, torna a API mais agradável de ser utilizada. Ainda que a segunda seqüência para chamada do método não seja muito melhor que a primeira, a API resultante é mais flexível. Em casos que o programador tem certeza de que a chamada será bem sucedida ou não se importa de deixar a thread terminar caso a chamada falhe, bastaria chamar
obj.action(args).

Item 42: Prefira o uso de exceções padrão
O reuso de código é uma boa prática também no caso de exceções. Torna a API mais fácil de aprender e usar porque segue convenções que os programadores já estão familiarizados. Um menor número de classes de exceção também incorre em menos memória necessária e menos tempo gasto carregando classes.
A exceção mais comumente reutilizada é a
IllegalArgumentException. Ela deve ser lançada quando  o método é chamado com um argumento cujo valor seja inapropriado. Outra exceção é a IllegalStateException, usada quando a chamada ao método é ilegal naquela situação, devido ao estado do objeto em que é chamado. De forma geral, qualquer chamada errada a um método cai em algum desses casos, mas outras exceções são utilizadas em certos tipos de estados ou argumentos ilegais. Por exemplo, se é passado um null onde ele não é aceito, é lançada a NullPointerException. Já no caso de um índice de uma seqüência que está fora dos limites, usa-se a IndexOutOfBoundsException. Outra exceção que vale a pena conhecer é a ConcurrentModificationexception, lançada quando há modificação simultânea em um objeto que deveria ser acessado apenas por uma thread. E a última que iremos mencionar é a UnsupportedOperationException, usada raramente, em casos que a classe não implementa alguns métodos opcionais definidos na interface.
Existem ainda muitas outras exceções utilizadas em casos mais específicos. É importante lembrar que o reuso precisa estar baseado na semântica, ou seja, compatível com a documentação da exceção, não apenas em seu nome. Além disso, você pode fazer uma subclasse de qualquer exceção caso queira adicionar um pouco mais de informação sobre o problema.

Item 43: Lance exceções apropriadas à abstração
Camadas mais altas devem tratar as exceções recebidas e, caso apropriado, em seu lugar lançar exceções mais adequadas em termos da abstração de alto nível. Exemplo:
try {
    //código
} catch(LowerLevelException e) {
    throw new HigherLevelException(...)
}

Uma forma especial de tradução de exceções, chamada encadeamento de exceções, é apropriada em casos que a exceção de baixo nível pode ser útil para debugar a situação que causou a exceção. Neste caso a exceção de baixo nível é armazenada como um objeto dentro da nova exceção lançada. A partir do Java 1.4 esse mecanismo é suportado diretamente pelo Throwable, sendo possível passar a exceção pelo construtor e dispensando a criação do campo e de um método getCause para obter o objeto.
Apesar da tradução de exceções ser superior à simples propagação das mesmas, este mecanismo não deve ser usado em excesso. O melhor é que a classe tente tomar as ações necessárias relativas àquela exceção, dispensando as camadas superiores de terem que tratá-la de alguma forma.

Item 44: Documente todas as exceções lançadas por cada método
A descrição das exceções lançadas é uma parte importante da documentação necessária para utilizar um método corretamente. Sempre declare as checked exceptions individualmente, e documente as condições exatas em que cada uma é lançada usando a tag
@throws do Javadoc. Não utilize simplesmente uma superclasse comum às exceções lançadas, pois isto torna obscuro o seu uso. As runtime exceptions, apesar de não precisarem ser declaradas, também devem estar documentadas para que o programador esteja familiarizado com os erros mais prováveis e possa evitá-los. Isto serve para a documentação das pré-condições de execução de um método. No caso de interfaces isto é particularmente importante, pois serve como o contrato geral e permite um comportamento comum entre suas múltiplas implementações.
Use a tag
@throws do Javadoc para documentar cada runtime exception que o método pode lançar, mas não utilize a palavra throws para incluí-las na declaração do método. Assim fica mais fácil para o programador que está usando a API identificar visualmente quais são suas responsabilidades no uso daquele método quando lê a documentação gerada pelo Javadoc.
Se uma exceção é lançada por diversos métodos da classe pela mesma razão, é aceitável documentá-la nos comentários da documentação da classe ao invés de individualmente em cada método.

Item 45: Inclua informações sobre a falha em mensagens de detalhe
O stack trace de uma exceção é sua representação em String, resultado do método
toString. Normalmente é constituída pelo nome da classe seguido de uma mensagem de detalhe. Isto deve retornar toda informação possível para ajudar a pessoa a diagnosticar a causa do problema, contendo os valores de todos os parâmetros e campos que "contribuíram para a exceção". Porém, lembre-se de que o stack trace será analisado por um programador junto com o código do método, portanto é desnecessário incluir informações facilmente observadas lendo o código diretamente e o conteúdo é bem mais importante do que a forma como ele é apresentado.
Para garantir que a exceção tratará todas as informações necessárias, seus construtores devem obrigar a inclusão das mesmas ao criar o objeto. Como sugerido no item 40, podem ser incluídos métodos accessors para o programador obter individualmente estes valores.

Item 46: Tente garantir atomicidade das falhas
Um método cuja chamada não deu certo deve manter o objeto no estado em que ele estava antes da chamada ao método.
Uma forma de conseguir isso é através de objetos imutáveis (item 13). No caso de objetos mutáveis, o melhor é checar os parâmetros antes de tentar realizar a operação (item 23), fazendo com que as exceções sejam lançadas antes de iniciar qualquer modificação do objeto. No caso de não ser possível checar os parâmetros, deve-se tentar realizar as operações que podem falhar antes daquelas que modificam o objeto. A terceira abordagem para atingir atomicidade das falhas é incluir código que volte o objeto ao estado anterior à operação. E a última forma é efetuar as operações em uma cópia temporária do objeto e substituir o conteúdo do objeto original com o da cópia após completar a operação.
No caso de objetos sendo acessados por várias threads ou de erros (ao invés de exceptions), dificilmente será possível atingir a atomicidade. Há também casos em que o aumento de complexidade não compensa, e então deve estar documentado claramente na API em que estado ficará o objeto caso a operação falhe.

Item 47: Não ignore exceções
É fácil simplesmente ignorar uma exceção incluindo um bloco catch vazio. Porém, isso vai contra os objetivos do mecanismo de exceções do Java; é como ignorar um alarme de incêndio e ainda desligá-lo para que mais ninguém tenha a chance de saber que algo está pegando fogo. Portanto, no mínimo deve haver um comentário dentro do bloco catch explicando porque a exceção está sendo ignorada.




Bibliografia:
Bloch, Joshua. Effective Java.

Resumo por: Vanessa Sabino
1