sábado, 21 de novembro de 2009

Não generalize, pois existem muitas exceções a regra!



"Uma exceção é um evento, que ocorre durante a execução de um programa, que interrompe o fluxo normal das instruções do programa."


Quando um erro ocorre dentro de um método, o método cria um objeto e manda-opara o sistema de execução(Runtime). O objeto, chamado de um objeto de exceção(Exception), contém informações sobre o erro, incluindo seu tipo e o estado do programa quando ocorreu o erro(StackTrace). Criando um objeto de exceção e entregá-lo para o sistema de execução é chamado de lançar(throw) uma exceção.

Depois de um método gerar uma exceção, o sistema de execução tenta achar alguem para lidar com a exceção. O conjunto de possíveis "algumas coisas" para tratar a exceção é a lista ordenada de métodos que tinha sido chamado para chegar ao método onde ocorreu o erro. A lista de métodos é conhecido como a pilha de chamadas (ver figura seguinte).




O sistema de tempo de execução procura a pilha de chamadas para um método que contém um bloco de código que pode manipular a exceção. Este bloco de código é chamado um manipulador de exceção. A busca começa com o método em que ocorreu o erro e continua através da pilha de chamadas na ordem inversa em que os métodos foram chamados. Quando um manipulador apropriado seja encontrado, o sistema de execução passa a exceção para o manipulador. Um manipulador de exceção é considerado adequado se o tipo do objeto de exceção acionada coincide com o tipo que pode ser tratada pelo manipulador.

O manipulador de exceção escolhido é dito para capturar a exceção. Se o sistema de execução de forma exaustiva procura um manipulador para exceção na pilha de chamadas, sem encontrar um manipulador de exceção apropriado, como mostrado na figura ao lado, o sistema de execução (e, conseqüentemente, o programa) é encerrado(A thread principal no programa, que roda o main, morre, pois nao "abriu o paraquedas"!).


Basicamente existem 3 tipos de exceções que podem ocorrer na JVM:

O primeiro tipo de exceção é a exceção verificada(checked). Estas são as condições excepcionais que uma aplicação bem escrita deve antecipar e recuperar. Por exemplo, suponha que um aplicativo solicita a um usuário para escrever um nome de arquivo de entrada, em seguida, abre o arquivo, passando o nome para o construtor para java.io.FileReader. Normalmente, o usuário fornece o nome de um arquivo, de forma legível, para a construção do objeto FileReader bem-sucedido, e para a execução do aplicativo continua normalmente. Mas às vezes o usuário fornece o nome de um arquivo inexistente, e o construtor lança(throws) um objeto java.io.FileNotFoundException. Um programa bem escrito vai pegar essa exceção e notificar o usuário do erro, possivelmente levando a um nome de arquivo corrigido.

O segundo tipo de exceção é o erro(error). Estas são as condições excepcionais que são externas ao aplicativo, e que a aplicação normalmente, a qual o programa não pode se antecipar ou se recuperar. Por exemplo, suponha que uma aplicação com êxito abre um arquivo para a entrada, mas é incapaz de ler o arquivo por causa de um hardware ou mau funcionamento do sistema. A leitura mal feita jogará java.io.IOError. Um aplicativo pode optar por pegar essa exceção, a fim de notificar o usuário do problema - mas também pode fazer sentido para o programa para imprimir um rastreamento de pilha e sair,sendo sempre bo pratica a segunda opção.

Os erros não estão sujeitos à requerimentos do tipo "pegar ou especificar". Os erros são as exceções indicadas pelo erro e suas subclasses.

O terceiro tipo de exceção é a exceção de runtime. Estas são as condições excepcionais que são internas à aplicação, e que a aplicação normalmente, não pode antecipar ou recuperar. Estes geralmente indicam erros de programação, como erros de lógica ou de utilização indevida de uma API. Por exemplo, considere a aplicação descrita anteriormente que passa um nome de arquivo para o construtor para FileReader. Se um erro de lógica faz com que um nulo para ser passado para o construtor, o construtor irá lançar NullPointerException. O aplicativo pode pegar essa exceção, mas provavelmente faz mais sentido para eliminar o erro que causou a exceção para ocorrer.

Exceções de runtime tambem não estão sujeitos à requerimentos do tipo "pegar ou especificar".

Exceções de runtime são as indicadas pela classe RuntimeException e suas subclasses.

Erros e exceções de runtime são conhecidas coletivamente como exceções nao checadas(unchecked).


//Note: Essa classe nao compila por design!
//O codigo abaixo mostra diversas má praticas de design
//,porem tem carater meramente didático para o tema do post
import java.io.*;
import java.util.Vector;

public class ListOfNumbers {

private Vector vector;
private static final int SIZE = 10;

public ListOfNumbers () {
vector = new Vector(SIZE);
for (int i = 0; i < SIZE; i++) {
vector.addElement(new Integer(i));
}
}

public void writeList() {
PrintWriter out = new PrintWriter(
new FileWriter("OutFile.txt"));

for (int i = 0; i < SIZE; i++) {
out.println("Value at: " + i + " = " +
vector.elementAt(i));
}

out.close();
}
}



O construtor inicializa um fluxo de saída em um arquivo. Se o arquivo não pode ser aberto, o construtor lança uma IOException. Aposisso, uma chamada ao método elementAt a classe Vector, que gera uma ArrayIndexOutOfBoundsException se o valor de seu argumento é muito pequeno (inferior a 0) ou muito grande (mais do que o número de elementos contidos no momento por vetores).

Se você tentar compilar a classe ListOfNumbers, o compilador imprime uma mensagem de erro sobre a exceção lançada pelo construtor FileWriter. No entanto, ele não exibir uma mensagem de erro sobre a exceção lançada pelo elementAt. A razão é que a exceção lançada pelo construtor, IOException, é uma exceção verificada(checked), e a acionada pelo método elementAt, ArrayIndexOutOfBoundsException, é uma exceção desmarcada(unchecked).

Agora que você está familiarizado com a classe ListOfNumbers e onde as exceções podem ser geradas dentro dela, você está pronto para escrever manipuladores de exceção para capturar e manipular as exceções.

Para construir um manipulador de exceção para o método da classe writeList ListOfNumbers, coloque o codigo que pod elançar uma exceção ,do método writeList dentro de um bloco try. Há mais do que uma maneira de fazer isso. Você pode colocar cada linha de código que pode lançar uma exceção dentro do seu próprio bloco try e fornecer manipuladores de exceção em separado para cada um. Ou, você pode colocar todo o código writeList dentro de um bloco único e tentar associar vários manipuladores com ele. A listagem a seguir usa um bloco try do método, pois todo o código em questão é muito curto.


//...codigo omitido
private Vector vector;
private static final int SIZE = 10;

PrintWriter out = null;

try {
System.out.println("Entered try statement");
out = new PrintWriter(new FileWriter("OutFile.txt"));
for (int i = 0; i < SIZE; i++) {
out.println("Value at: " + i + " = "
+ vector.elementAt(i));
}
}
// catch e finally



Se ocorrer uma exceção dentro do bloco try, essa exceção é tratado por um manipulador de exceção a ela associados. Para associar um manipulador de exceção com um bloco try, você deve colocar um bloco catch depois do bloco try..


try {

} catch (ExceptionType name) {

} catch (ExceptionType name) {

}


Cada bloco catch é um manipulador de exceção e manipula o tipo de exceção indicada pelo seu argumento. O tipo de argumento, ExceptionType, declara o tipo de exceção que o manipulador pode manipular e deve ser o nome de uma classe que herda da classe Throwable. O manipulador pode referir-se a exceção com o nome.

O bloco catch contém código que é executado, se e quando o manipulador de exceção é invocado. O sistema de execução chama o manipulador de exceção quando o manipulador é o primeiro na pilha de chamadas cuja ExceptionType corresponde ao tipo da exceção lançada. O sistema considera que é um jogo jogado, se o objeto pode ser legalmente atribuído ao argumento do manipulador de exceção.

Seguem dois manipuladores de exceção para o método writeList - um para dois tipos de exceções verificadas que podem ser jogadas dentro da declaração try.




try {

} catch (FileNotFoundException e) {
System.err.println("FileNotFoundException: "
+ e.getMessage());
throw new SampleException(e);

} catch (IOException e) {
System.err.println("Caught IOException: "
+ e.getMessage());
}



Ambos os manipuladores imprimem uma mensagem de erro. O segundo manipulador faz mais nada. Pela captura de qualquer IOException que não é capturada pelo primeiro manipulador , ele permite que o programa continue executando.
O primeiro manipulador, além de imprimir uma mensagem, gera uma exceção definida pelo usuário. Neste exemplo, quando o FileNotFoundException é pego, ele cria uma exceção definida pelo usuário chamado SampleException para ser acionada. Você pode querer fazer isso se você quiser que o seu programa possa tratar uma exceção desta situação de uma maneira específica.

Manipuladores de exceção podem fazer mais do que apenas imprimir mensagens de erro ou parar o programa. Eles podem fazer a recuperação de erro, solicitar que o usuário tome uma decisão, ou propagar o erro até um manipulador de nível superior com exceções em cadeia.


Um bloco finally sempre é executado quando um bloco try ou catch retorna. Isso garante que o bloco finally será executado mesmo se ocorrer uma exceção inesperada. Mas, finalmente, é útil para mais do que apenas a manipulação de exceção - que permite o programador para evitar que um código de limpeza de recursos acidentalmente seja contornado por um return, continue, ou break. Colocar código de limpeza em um bloco finally é sempre uma boa prática, mesmo quando não há exceções são previstas.


Importante: O bloco finally é um instrumento fundamental para a prevenção de vazamentos de recursos. Ao fechar um arquivo ou recuperação de recursos, colocar o código em um bloco finally para garante que o recurso é sempre recuperado.


//metodo completo apos o refactoring para inserir tratamento de exceções!

public void writeList() {
PrintWriter out = null;

try {
System.out.println("Entering try statement");
out = new PrintWriter(
new FileWriter("OutFile.txt"));
for (int i = 0; i < SIZE; i++)
out.println("Value at: " + i + " = "
+ vector.elementAt(i));

} catch (ArrayIndexOutOfBoundsException e) {
System.err.println("Caught "
+ "ArrayIndexOutOfBoundsException: "
+ e.getMessage());

} catch (IOException e) {
System.err.println("Caught IOException: "
+ e.getMessage());

} finally {
if (out != null) {
System.out.println("Closing PrintWriter");
out.close();

}
else {
System.out.println("PrintWriter not open");
}
}
}


Referências
Tutorial Oficial da Sun sobre Java, capitulo Exceptions

Artigos
Como lançar suas próprias exceções

Exceções de runtime, A Controversia


Espero ter ajudado com este post!
Obrigado pelo seu tempo!
Participem do OSUM, sua participação é o mais importante!

Marcadores:

0 Comentários:

Postar um comentário

Assinar Postar comentários [Atom]

<< Página inicial