A razão pela qual as exceções são um poderoso método de depuração é que eles respondem às três perguntas a seguir:
1. O que deu errado?
2. Onde você cometeu um erro?
3. Por que algo deu errado?
No caso de uso eficaz de exceções, o tipo de exceção responde "o que" é jogado, a exceção de rastreamento de pilha de respostas "onde" é lançada, as informações de exceção respondem "por que" é lançado e se sua exceção não responder a todas as perguntas acima, você não poderá usá -las bem.
Existem três princípios que podem ajudá -lo a maximizar o uso de boas exceções durante a depuração. Esses três princípios são:
1. Específico e claro
2. Jogue cedo
3. Captura de atraso
Para explicar esses três princípios de manipulação eficaz de exceções, este artigo discute isso, fabricando um gerente financeiro pessoal Jcheckbook, usado para registrar e rastrear atividades de conta bancária, como depósitos e saques e emissão de contas.
Concreto e claro
Java define uma hierarquia de uma classe de exceção, que começa com arremesso, estende o erro e a exceção, e a exceção estende a RunTimeException. Como mostrado na Figura 1.
Essas quatro classes são generalizadas e não fornecem muitas informações de erro. Embora instanciar essas classes seja sintaticamente legal (como: new Throwable ()), é melhor tratá -las como classes de base virtual e usar suas subclasses mais especializadas. O Java forneceu um grande número de subclasses de exceção. Se você quiser ser mais específico, também pode definir sua própria classe de exceção.
Por exemplo: o pacote java.io define a subclasse da classe Exception IoException, que é mais especializada e subclasse da IOException, como filenotfoundException, eofexception e objectStreamException. Cada um descreve uma classe específica de erros de E/S: perda de arquivo, final de arquivo de exceção e fluxos de objetos serializados por erro. Quanto mais específica a exceção, nosso programa será capaz de responder à pergunta "o que deu errado".
Também é importante tentar ficar o mais claro possível ao capturar exceções. Por exemplo: o JcheckBook pode lidar com o FILENOTFOUNDEXCECTION, re-intervendo o nome do arquivo do usuário. Para a Eofexception, ele pode continuar sendo executado com base nas informações lidas antes que a exceção seja lançada. Se uma objectStreamException for lançada, o programa deve solicitar ao usuário que o arquivo está corrompido e um arquivo de backup ou outro arquivo deve ser usado.
O Java facilita a captura explicitamente exceções, porque podemos definir vários blocos de captura para o mesmo bloco de tentativa, para que cada exceção possa ser tratada adequadamente separadamente.
Arquivo prefsfile = novo arquivo (prefsfilename); try{ readPreferences(prefsFile);}catch (FileNotFoundException e){ // alert the user that the specified file // does not exist}catch (EOFException e){ // alert the user that the end of the file // was reached}catch (ObjectStreamException e){ // alert the user that the file is corrupted}catch (IOException e){ // alert o usuário que ocorreu outro erro de E/o //}O JcheckBook fornece aos usuários informações explícitas sobre a captura de exceções usando vários blocos de captura. Por exemplo: se o FileNotFoundException for capturado, poderá solicitar ao usuário especificar outro arquivo. Em alguns casos, o esforço adicional de codificação trazido por vários blocos de captura pode ser uma carga não essencial, mas neste exemplo, o código extra ajuda o programa a fornecer uma resposta mais fácil de usar.
Além das exceções tratadas pelos três primeiros blocos de captura, o último bloco de captura fornece ao usuário informações de erro mais generalizadas quando a IoException for lançada. Dessa forma, o programa pode fornecer informações específicas o máximo possível, mas também tem a capacidade de lidar com outras exceções inesperadas.
Às vezes, os desenvolvedores capturam exceções normalizadas e exibem o nome da classe de exceção ou as informações da pilha de impressão para "especificidade". Não faça isso! Os usuários só terão dor de cabeça ao ver java.io.EOFException ou informações da pilha, em vez de obter ajuda. Exceções específicas devem ser capturadas e o usuário deve ser solicitado com "palavras humanas". No entanto, a pilha de exceção pode ser impressa em seu arquivo de log. Lembre -se de que exceções e informações da pilha são usadas para ajudar os desenvolvedores e não os usuários.
Por fim, deve -se notar que o JcheckBook não captura exceções no readPreferences() , mas deixa as exceções de captura e manuseio à camada de interface do usuário, para que os usuários possam ser notificados usando caixas de diálogo ou outros métodos. Isso é chamado de "captura de atraso", que será discutida abaixo.
Jogue fora cedo
As informações da pilha de exceção fornecem a ordem exata da cadeia de chamadas de método que faz com que a exceção ocorra, incluindo o nome da classe, o nome do método, o nome do arquivo e o número até a linha de cada chamada de método, a fim de localizar com precisão a cena em que a exceção ocorre.
java.lang.nullPointerExceptionAt java.io.fileInputStream.open (método nativo) em java.io.fileInputStream. jcheckbook.jcheckbook.startup (jcheckbook.java:116) em jcheckbook.jcheckbook.
O acima mostra o caso em que o método Open () da classe FileInputStream lança uma NullPointerException. No entanto, observe que FileInputStream.close() faz parte da biblioteca de classes Java padrão e é provável que o problema dessa exceção seja que nosso código em si não seja a API Java. Portanto, é provável que o problema apareça em um dos métodos anteriores e, felizmente, ele também é impresso nas informações da pilha.
Infelizmente, o NullPointerException é a exceção menos informativa (mas também a mais comum e falhada) em Java. Não menciona o que mais nos preocupamos: onde está nulo. Então, tivemos que dar alguns passos para trás e descobrir onde algo deu errado.
Ao backup das informações da pilha de rastreamento e verificando o código, podemos determinar que a causa do erro é que um parâmetro de nome de arquivo vazio foi passado para readPreferences() . Como readPreferences() sabe que ele não pode lidar com nomes de arquivos vazios, verifique a condição imediatamente:
public void readPreferences (nome do arquivo de string) lança ilegalArgumentException {if (filename == null) {tire nova ilegalArgumentException ("nome do arquivo é nulo"); } // if //...Perform outras operações ... inputStream em = new FileInputStream (nome do arquivo); //...Rereça o arquivo de preferências ...} Ao jogar uma exceção cedo (também conhecida como "Falha rapidamente"), a exceção é clara e precisa. As informações da pilha refletem imediatamente o que deu errado (os valores dos parâmetros ilegais foram fornecidos), por que deu errado (o nome do arquivo não pode ser nulo) e onde deu errado (a primeira parte do readPreferences() ). Dessa forma, nossas informações de pilha podem ser fornecidas com sinceridade:
java.lang.illegalargumentException: o nome do arquivo é nullat jcheckbook.jcheckbook.readPreferences (jcheckbook.java:207) em jcheckbook.jcheckbook.startup (jcheckbook.java:116) at JCheck.jCheckBook. jcheckbook.jcheckbook.main (jcheckbook.java:318)
Além disso, as informações de exceção contidas nele ("Nome do arquivo está vazio") tornam a exceção mais rica, respondendo explicitamente à pergunta do que está vazio, que não está disponível para nós no código anterior.
A falha é alcançada jogando uma exceção imediatamente quando um erro é detectado, a construção desnecessária de objetos ou o uso de recursos, como conexão de arquivo ou rede, pode ser efetivamente evitada. Da mesma forma, as operações de limpeza provocadas ao abrir esses recursos podem ser salvas.
Captura atrasada
Um erro que novatos e mestres podem cometer é pegar o programa antes de ter a capacidade de lidar com a exceção. O compilador Java facilita indiretamente esse comportamento, exigindo que a exceção verificada seja capturada ou jogada. A maneira natural é embrulhar imediatamente o código em um bloco de tentativa e usar a captura para capturar exceções para evitar o erro do compilador.
A questão é: o que devo fazer se tiver a exceção depois de ser pego? A pior coisa a fazer é não fazer nada. Um bloco de captura vazio é equivalente a jogar toda a exceção no buraco negro e todas as informações que podem explicar quando, onde, por que os erros estão errados serão perdidos para sempre. É um pouco melhor escrever exceções no log, pelo menos há registros para verificar. Mas não podemos esperar que os usuários leiam ou compreendam arquivos de log e informações de exceção. Também não é apropriado ter readPreferences() exibir uma caixa de diálogo de mensagem de erro, porque, embora atualmente o JcheckBook seja um aplicativo de desktop, também planejamos transformá-lo em um aplicativo da Web baseado em HTML. Nesse caso, a exibição de uma caixa de diálogo de erro obviamente não é uma opção. Ao mesmo tempo, independentemente da versão HTML ou C/S, as informações de configuração são lidas no servidor e a mensagem de erro precisa ser exibida no navegador da Web ou no programa cliente. readPreferences() também deve levar em consideração essas necessidades futuras ao projetar. A separação adequada do código da interface do usuário e da lógica do programa pode melhorar a reutilização do nosso código.
Pegar uma exceção prematuramente antes do manuseio condicional, geralmente resulta em erros mais graves e outras exceções. Por exemplo, se readPreferences() acima capturar e registrar imediatamente a FileNotFoundException que pode ser jogada quando o construtor FileInputStream for chamado, o código se tornará assim:
public void readPreferences (string filename) {// ... inputStream in = null; // não faça isso !!! tente {in = new FileInputStream (nome do arquivo);} catch (filenotfoundException e) {logger.log (e);} in.read (...); // ...}O código acima o captura sem a capacidade de se recuperar da FilENotFoundException. Se o arquivo não puder ser encontrado, o método a seguir obviamente não poderá lê -lo. O que acontece se o readPreferences () for solicitado a ler um arquivo que não existe? Obviamente, a FileNotFoundException será registrada e, se analisássemos o arquivo de log naquele momento, saberíamos. No entanto, o que acontece quando um programa tenta ler dados de um arquivo? Como o arquivo não existe, a variável está vazia e uma NullPointerException será lançada.
Ao depurar um programa, o instinto nos diz para examinar as informações no final do log. Isso seria NullPointerException, e o que é muito irritante é que essa exceção é muito inespecífica. A mensagem de erro não apenas nos engana o que deu errado (o erro real é o FILENOTFoundException em vez do NullPointerException), mas também engana a fonte do erro. O problema real está fora do número de linhas na NullPointerException, que pode ter várias chamadas de método e destruição de classes. Nossa atenção foi atraída do erro real pelo pequeno peixe até que olhamos para o tronco para encontrar a fonte do problema.
Como o que readPreferences() realmente deve fazer é não pegar essas exceções, o que deveria ser? Parece um pouco contra -intuitivo, e a maneira mais apropriada é não fazer nada e não captar exceções imediatamente. Deixe a responsabilidade para o chamador do readPreferences() e deixe estudar a maneira apropriada de lidar com os arquivos de configuração ausentes. Pode levar ao usuário especificar outros arquivos ou usar o valor padrão. Se realmente não funcionar, pode alertar o usuário e sair do programa.
A maneira de passar a responsabilidade pelo manuseio de exceção a montante da cadeia de chamadas é declarar a exceção na cláusula de arremesso do método. Ao declarar possíveis exceções, tenha cuidado, quanto mais específico, melhor. Isso é usado para identificar o tipo de exceção que o programa chamando seu método precisa conhecer e estar preparado para lidar. Por exemplo, a versão "Captura de atraso" do readPreferences() pode ser assim:
public void readPreferences (nome do arquivo de string) lança ilegalArgumentException, filenotfoundException, ioexception {if (filename == null) {lança nova ilegalArgumentException ("nome do arquivo é nulo"); } // se // ... inputStream em = new FileInputStream (nome do arquivo); // ...}Tecnicamente, a única exceção que precisamos declarar é uma IoException, mas declaramos explicitamente que o método pode lançar uma FileNotFoundException. IllegalargumentException não é necessário para ser declarado porque é uma exceção não verificação (ou seja, uma subclasse da RuntimeException). No entanto, é declarado documentar nosso código (essas exceções também devem ser marcadas nos javadocs do método).
Obviamente, eventualmente seu programa precisa capturar a exceção, caso contrário, ele terá rescisão inesperadamente. Mas o truque aqui é capturar exceções no nível certo, para que seu programa possa se recuperar significativamente das exceções e continuar sem causar erros mais profundos; Ou ser capaz de fornecer aos usuários informações claras, incluindo orientá -los para se recuperar de erros. Se o seu método não for competente, não lide com a exceção, deixe -o para trás para pegá -lo e lidar com ele no nível certo.
Resumir
Os desenvolvedores experientes sabem que a maior dificuldade em depurar programas não é corrigir defeitos, mas descobrir os esconderijos de defeitos de uma quantidade enorme de código. Desde que você siga os três princípios deste artigo, suas exceções podem ajudá-lo a rastrear e eliminar defeitos, tornar seu programa mais robusto e fácil de usar. O exposto acima é o conteúdo inteiro deste artigo, e espero que o ajude em seu estudo ou trabalho.