English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Três princípios para o tratamento de exceções Java

A força das exceções como ferramenta de depuração está em responder às seguintes três perguntas:

     1、O que está errado?63;

     2、Onde está o erro?63;

     3、Porque está errado?63;

Quando usado eficazmente, o tipo de exceção responde a 'o que foi lançado', o rastreamento de pilha de exceções responde a 'onde foi lançado', e as informações da exceção respondem a 'porque foi lançado'. Se suas exceções não responderem a todas essas perguntas, pode ser que você não esteja usando-as corretamente.

Existem três princípios que ajudam a usar ao máximo as exceções durante a depuração, são eles:

     1、Específico e claro

     2、Lançamento antecipado

     3、Atraso na captura

Para ilustrar os três princípios de tratamento eficaz de exceções, este artigo discute a classe de gerenciamento financeiro pessoal JCheckbook, usada para registrar e rastrear atividades de contas bancárias, como depósitos e saques, emissão de documentos.

Específico e claro

O Java define uma hierarquia de classes de exceções, começando com Throwable, se estendendo para Error e Exception, e Exception se estendendo para RuntimeException. Como mostrado na figura1Mostrado.

Essas quatro classes são genéricas e não fornecem muitas informações de erro, embora a instância dessas classes seja gramaticalmente válida (como: new Throwable()), é melhor tratá-las como classes base virtuais, usando suas subclasses mais especializadas. O Java já fornece uma quantidade significativa de subclasses de exceções, e se necessário, você também pode definir suas próprias classes de exceção.

Por exemplo: o pacote java.io define subclasses da classe Exception, como IOException, que são mais especializadas, como FileNotFoundException, EOFException e ObjectStreamException, que são subclasses de IOException. Cada uma delas descreve um tipo específico de I/Erros O: incluem perda de arquivo, final de arquivo inválido e objetos de fluxo de serialização incorretos. Quanto mais específica a exceção, melhor nossa programação pode responder à pergunta 'o que deu errado'.

Ao capturar exceções, é importante tentar ser o mais claro possível. Por exemplo: JCheckbook pode lidar com FileNotFoundException perguntando novamente ao usuário o nome do arquivo, para EOFException, ele pode continuar a executar com base na informação lida antes da exceção ser lançada. Se lançada for ObjectStreamException, o programa deve informar ao usuário que o arquivo está corrompido e deve usar um arquivo de backup ou outro arquivo.

Java facilita a captura explícita de exceções, pois podemos definir múltiplos blocos catch para o mesmo bloco try, tratando cada tipo de exceção de maneira apropriada.

File prefsFile = new File(prefsFilename);
try{
  readPreferences(prefsFile);
}
catch (FileNotFoundException e){
  // alertar o usuário de que o arquivo especificado
  // não existe
}
catch (EOFException e){
  // alertar o usuário de que o final do arquivo
  // foi alcançado
}
catch (ObjectStreamException e){
   // alertar o usuário de que o arquivo está corrompido
}
catch (IOException e){
  // alertar o usuário de que houve outro I/O
  // ocorreu um erro
}

JCheckbook fornece informações claras sobre as exceções capturadas ao usuário através do uso de múltiplos blocos catch. Por exemplo: se um FileNotFoundException for capturado, ele pode notificar o usuário para especificar outro arquivo. Em alguns casos, o trabalho adicional de codificação带来了额外的编码工作量可能是不必要的负担,mas neste exemplo, o código adicional realmente ajuda o programa a fornecer uma resposta mais amigável ao usuário.

Além das exceções tratadas pelos três primeiros blocos catch, o último bloco catch fornece informações de erro mais genéricas quando 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 genéricas e exibem o nome da classe da exceção ou imprimem informações de pilha de chamadas para tentar “especificar”. NÃO faça isso! O usuário verá java.io.EOFException ou informações de pilha de chamadas e apenas terá dores de cabeça, não ajuda. Deve-se capturar exceções específicas e fornecer informações precisas ao usuário em “linguagem humana”. No entanto, as informações de pilha de exceção podem ser impressas no seu arquivo de log. Lembre-se, exceções e informações de pilha de chamadas são usadas para ajudar os desenvolvedores, não os usuários.

Finalmente, deve-se notar que JCheckbook não captura exceções em readPreferences(), mas deixa a captura e o tratamento das exceções para a camada de interface do usuário, o que permite notificar o usuário de forma dialogada ou por outros meios. Isso é conhecido como “captura atrasada”, que será discutido a seguir.

lançar cedo

As informações de pilha de exceção fornecem a sequência exata de chamadas de métodos que levaram ao surgimento da exceção, incluindo o nome da classe, o nome do método, o nome do arquivo de código e até mesmo o número da linha, para localizar exatamente onde a exceção ocorreu.

java.lang.NullPointerException
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:103)
at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:225)
at jcheckbook.JCheckbook.startup(JCheckbook.java:116)
at jcheckbook.JCheckbook.<init>(JCheckbook.java:27)
at jcheckbook.JCheckbook.main(JCheckbook.java:318)

O acima ilustra o caso de o método open() da classe FileInputStream lançar NullPointerException. No entanto, note que FileInputStream.close() é parte da biblioteca padrão Java, e o problema que provavelmente cause essa exceção está no nosso código, não na API Java. Portanto, o problema provavelmente está em um dos métodos anteriores, felizmente ele também está impresso nas informações de pilha.

Infelizmente, o NullPointerException é a exceção mais informativa (apesar de ser a mais comum e desesperançadora) em Java. Ele nem menciona o que mais nos importa: onde está null. Portanto, temos que recuar alguns passos para encontrar onde o erro ocorreu.

Através de rastreamento gradual de informações de pilha e verificação do código, podemos determinar que a causa do erro foi passar um parâmetro de nome de arquivo nulo para readPreferences(). Como readPreferences() sabe que não pode lidar com nome de arquivo nulo, ele verifica essa condição imediatamente:

public void readPreferences(String filename)
throws IllegalArgumentException{
  if (filename == null){
     throw new IllegalArgumentException("filename is null");
  } //if
  //...executar outras operações...
  InputStream in = new FileInputStream(filename);
  //...ler o arquivo de preferências...
}

Através de lançamento antecipado de exceções (também conhecido como 'falha rápida'), as exceções são claras e precisas. As informações de pilha imediatamente refletem o que deu errado (forneceu valor de parâmetro ilegal), por que deu errado (o nome do arquivo não pode ser nulo) e onde deu errado (parte inicial de readPreferences()). Dessa forma, nossas informações de pilha podem fornecer de maneira realista:

java.lang.IllegalArgumentException: filename is null
at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:207)
at jcheckbook.JCheckbook.startup(JCheckbook.java:116)
at jcheckbook.JCheckbook.<init>(JCheckbook.java:27)
at jcheckbook.JCheckbook.main(JCheckbook.java:318)

Além disso, as informações de exceção contidas ("Nome do arquivo vazio") tornam as informações fornecidas pela exceção mais ricas, respondendo explicitamente à pergunta sobre o que está vazio, algo que a NullPointerException lançada no nosso código anterior não pode fornecer.

Realizar falhas rápidas ao lançar exceções ao detectar erros pode evitar a construção desnecessária de objetos ou a ocupação de recursos, como arquivos ou conexões de rede. Além disso, as operações de limpeza necessárias para abrir esses recursos também podem ser evitadas.

Atraso na captura

Erros que tanto iniciantes quanto veteranos podem cometer são capturar exceções antes de que o programa tenha a capacidade de lidar com elas. O compilador Java incentiva这种行为 por exigir que todas as exceções detectadas sejam capturadas ou lançadas. A abordagem natural é imediatamente envolver o código em um bloco try e usar catch para capturar exceções, para evitar que o compilador gere erros.

O problema é o que fazer após capturar a exceção? O pior que se pode fazer é nada. Um bloco catch vazio é equivalente a jogar a exceção em um buraco negro, perdendo todas as informações que poderiam explicar quando, onde e por que houve um erro. Escrever a exceção no log é um pouco melhor, pelo menos temos um registro para consultar. Mas não podemos esperar que o usuário leia ou entenda o arquivo de log e as informações de exceção. Não é apropriado exibir uma caixa de diálogo de erro para readPreferences(), porque, embora o JCheckbook seja atualmente um aplicativo de desktop, planejamos torná-lo uma aplicação web baseada em HTML. Nesse caso, exibir uma caixa de diálogo de erro não é uma opção. Além disso, independentemente do HTML ou do C/Na versão S, as informações de configuração são lidas no servidor, enquanto as informações de erro precisam ser exibidas para o navegador da Web ou o programa cliente. readPreferences() deve considerar essas necessidades futuras no design. A separação apropriada do código da interface do usuário e da lógica do programa pode aumentar a reutilização do nosso código.

Capturar exceções de forma prematura antes de lidar com elas pode levar a erros mais graves e outras exceções. Por exemplo, se o método readPreferences() do texto anterior capturar e registrar imediatamente a FileNotFoundException que pode ser lançada no construtor FileInputStream, o código ficará assim:

public void readPreferences(String filename){
  //...
  InputStream in = null;
  // NÃO FAÇA ISTO!!!
try{
  in = new FileInputStream(filename);
}
catch (FileNotFoundException e){
  logger.log(e);
}
in.read(...);
//...
}

O código acima captura FileNotFoundException sem ter a capacidade de se recuperar dela. Se o arquivo não puder ser encontrado, o método abaixo claramente não pode lê-lo. O que acontece se o readPreferences() for solicitado a ler um arquivo inexistente? Claro, FileNotFoundException será registrado, se olharmos o arquivo de log. No entanto, o que acontece quando o programa tenta ler dados do arquivo? Como o arquivo não existe, a variável 'in' está vazia, e um NullPointerException será lançado.

Durante a depuração do programa, a intuição nos diz para olhar as informações no final do log. Isso será NullPointerException, e o que é muito irritante é que essa exceção é muito vaga. As informações de erro não só nos enganam sobre o que está errado (o erro real é FileNotFoundException, não NullPointerException), mas também nos enganam sobre a origem do erro. O problema real está várias linhas além do ponto onde NullPointerException é lançado, onde podem haver várias chamadas de métodos e destruição de classes. Nossa atenção é atraída por essa 'pequena peixe' longe do erro real, até que olhemos para trás no log para descobrir a verdadeira fonte do problema.

Se o que o readPreferences() realmente deve fazer não é capturar essas exceções, o que deve ser? Parece um pouco contraditório, mas a prática mais apropriada, na verdade, é fazer nada, não capturar a exceção imediatamente. Delegue a responsabilidade para o chamador do readPreferences(), que pode estudar a maneira correta de lidar com a ausência do arquivo de configuração, possivelmente sugerindo ao usuário um arquivo diferente ou usando um valor padrão. Se não houver outra opção, talvez avise o usuário e encerre o programa.

A maneira de passar a responsabilidade de tratamento de exceções para o topo da chamada de métodos é declarar a exceção no trecho throws da função. Ao declarar as exceções que podem ser lançadas, prefira o mais específico possível. Isso serve para identificar os tipos de exceções que o programa que chama sua função precisa saber e estar preparado para lidar. Por exemplo, a versão 'delayed catch' do readPreferences() pode ser assim:

public void readPreferences(String filename)
lança IllegalArgumentException;,
FileNotFoundException, IOException{
  if (filename == null){
      throw new IllegalArgumentException("filename is null");
   } //if
   //...
   InputStream in = new FileInputStream(filename);
//...
}

Técnicamente, a única exceção que precisamos declarar é IOException, mas declaramos claramente que o método pode lançar FileNotFoundException. IllegalArgumentException não é obrigatório declarar, pois é uma exceção não verificada (ou seja, subclasse de RuntimeException). No entanto, declaramos isso para documentar nosso código (essas exceções também devem ser anotadas nos JavaDocs do método).

Claro, seu programa precisa capturar exceções, senão ele pode terminar inesperadamente. No entanto, a técnica aqui é capturar exceções em uma camada apropriada, para que seu programa possa se recuperar significativamente de exceções e continuar, sem causar erros mais profundos; ou fornecer informações claras aos usuários, incluindo guias para recuperação de erros. Se seu método não for adequado, não processe a exceção, deixe para capturar e lidar em uma camada apropriada.

Resumo

Desenvolvedores experientes sabem que o maior desafio da depuração de programas não está em corrigir falhas, mas em encontrar onde as falhas estão escondidas em um código vasto. Seguindo os três princípios deste artigo, você pode fazer com que suas exceções o ajudem a rastrear e eliminar falhas, tornando seus programas mais robustos e amigáveis aos usuários. Este é o conteúdo completo do artigo, esperando que ele ajude seus estudos ou trabalho de alguma forma.

Declaração: O conteúdo deste artigo é extraído da Internet, pertence ao respectivo proprietário. O conteúdo é contribuído e carregado voluntariamente pelos usuários da Internet. Este site não possui direitos de propriedade, não foi editado manualmente e não assume responsabilidade legal relevante. Se você encontrar conteúdo suspeito de infringir direitos autorais, por favor, envie um e-mail para: notice#oldtoolbag.com (ao enviar e-mail, substitua # por @ para denunciar e forneça provas relevantes. Em caso de verificação, o site deletará imediatamente o conteúdo suspeito de infringir direitos autorais.)

Você também pode gostar