Por que re-throw exceções?
-
02-07-2019 - |
Pergunta
Eu vi o seguinte código muitas vezes:
try
{
... // some code
}
catch (Exception ex)
{
... // Do something
throw new CustomException(ex);
// or
// throw;
// or
// throw ex;
}
Pode me explicar o propósito de re-lançar uma exceção? Trata-se de seguir a / melhor prática padrão no tratamento de exceção? (Eu tenho algum lugar leitura que é chamado de "Caller informar" padrão?)
Solução
Relançando a mesma exceção é útil se você quiser, por exemplo, registrar a exceção, mas não lidar com isso.
Jogando uma nova exceção que envolve a exceção capturada é bom para a abstração. por exemplo, a biblioteca utiliza uma biblioteca de terceiros que lança uma exceção que os clientes de sua biblioteca não deve conhecer. Nesse caso, você envolvê-la em um tipo de exceção mais nativa para a sua biblioteca, e jogar que em vez.
Outras dicas
Na verdade, há uma diferença entre
throw new CustomException(ex);
e
throw;
O segundo irá preservar as informações de pilha.
Mas às vezes você quer fazer a exceção mais "amigável" para o seu domínio de aplicação, em vez de deixar o DatabaseException chegar ao seu GUI, você vai aumentar a sua exceção personalizada que contém a exceção original.
Por exemplo:
try
{
}
catch (SqlException ex)
{
switch (ex.Number) {
case 17:
case 4060:
case 18456:
throw new InvalidDatabaseConnectionException("The database does not exists or cannot be reached using the supplied connection settings.", ex);
case 547:
throw new CouldNotDeleteException("There is a another object still using this object, therefore it cannot be deleted.", ex);
default:
throw new UnexpectedDatabaseErrorException("There was an unexpected error from the database.", ex);
}
}
Às vezes você quer esconder os detalhes de implementação de um método ou melhorar o nível de abstração de um problema de modo que é mais significativo para o chamador de um método. Para fazer isso, você pode interceptar a exceção original e substituto uma exceção personalizada que é mais adequado para explicar o problema.
Tomemos por exemplo um método que carrega os detalhes do usuário solicitado de um arquivo de texto. O método assume que um arquivo de texto existe nomeado com o ID do usuário e um sufixo de “.data”. Quando esse arquivo não existe realmente, não faz muito sentido para lançar uma FileNotFoundException porque o fato de que os detalhes de cada usuário são armazenados em um arquivo de texto é um detalhe de implementação interna para o método. Portanto, este método poderia, em vez enrole a exceção original em uma exceção personalizada com uma mensagem explicativa.
Ao contrário do código que você está demonstrado, a melhor prática é que a exceção original deve ser mantido por carregá-lo como a propriedade InnerException da sua nova exceção. Isto significa que um desenvolvedor pode ainda analisar o problema subjacente, se necessário.
Quando você está criando uma exceção personalizada, aqui está uma lista de verificação útil:
• Encontrar um nome bom que transmite por que a exceção foi lançada e certifique-se que as extremidades de nome com a palavra “exceção”.
• Certifique-se de implementar os três construtores de exceção padrão.
• Certifique-se de marcar a sua exceção com o atributo Serializable.
• Certifique-se de implementar o construtor desserialização.
• Adicione quaisquer propriedades de exceção personalizada que desenvolvedores possam ajudar a compreender e lidar com a sua exceção melhor.
• Se você adicionar as propriedades personalizadas, certifique-se de que você implementar e substituir GetObjectData para serializar suas propriedades personalizadas.
• Se você adicionar as propriedades personalizadas, substituir a propriedade mensagem de modo que você pode adicionar suas propriedades para a mensagem de exceção padrão.
• Lembre-se de anexar a exceção original usando a propriedade InnerException de sua exceção personalizada.
Você normalmente pegar e re-lance para uma de duas razões, dependendo de onde o código fica arquitetura dentro de uma aplicação.
No núcleo de um aplicativo que normalmente captura e re-lance para traduzir uma exceção em algo mais significativo. Por exemplo, se você está escrevendo uma camada de acesso de dados e utilização de códigos de erro personalizadas com o SQL Server, você pode traduzir SqlException em coisas como ObjectNotFoundException. Isso é útil porque (a) torna-se mais fácil para os chamadores tipos alça específicas de exceção, e (b) porque impede detalhes de implementação dessa camada como o fato de que você está usando SQL Server para persistência vazando para outras camadas, que permite-lhe mudar as coisas no futuro com mais facilidade.
No limites de aplicações é comum captura e re-lance sem traduzir uma exceção para que você pode entrar detalhes do mesmo, auxiliando na depuração e diagnóstico de problemas ao vivo. Idealmente você quer publicar em algum erro que a equipe de operações pode facilmente monitorar (por exemplo, o registo de eventos), bem como em algum lugar que dá contexto em torno de onde a exceção aconteceu no fluxo de controle para desenvolvedores (tipicamente rastreamento).
Eu posso pensar das seguintes razões:
-
Mantendo o conjunto de tipos de exceção lançada fixos, como parte da API, para que os chamadores só tem que se preocupar com o conjunto fixo de exceções. Em Java, você está praticamente forçado a fazer isso, por causa do mecanismo de exceções verificadas.
-
Adicionando algumas informações de contexto para a exceção. Por exemplo, em vez de deixar o "registro não encontrado" bare passar através do DB, você pode querer pegá-lo e adicionar "... durante o processamento de ordem XXX, procurando YYY produto".
-
Fazer alguma limpeza -. Fechamento de arquivos, rolando transações de volta, liberando algumas alças
Geralmente o "Algo Do" quer envolve explicar melhor a exceção (por exemplo, envolvendo-o em outra exceção), ou informações de rastreamento através de uma determinada fonte.
Outra possibilidade é se o tipo de exceção não é informação suficiente para saber se um necessidades de exceção de ser pego, caso em que a captura-lo um exame que irá fornecer mais informações.
Isto não quer dizer que o método é usado para puramente boas razões, muitas vezes ele é usado quando um desenvolvedor pensa informações de rastreamento pode ser necessário em algum momento futuro, caso em que você começa try {} catch {throw;} estilo , que não é útil a todos.
Eu acho que depende do que você está tentando fazer com a exceção.
Um bom motivo seria a de registrar o erro em primeiro lugar na captura, e depois jogá-lo até a interface do usuário para gerar uma mensagem de erro amigável com a opção de ver uma visão mais "avançado / detalhado" do erro, que contém o erro original.
Outra abordagem é uma abordagem "nova tentativa", por exemplo, uma contagem de erro é mantido, e depois de uma certa quantidade de tentativas que a única vez que o erro é enviado até a pilha (isto é feito às vezes para acesso de banco de dados para chamadas de banco de dados que tempo limite, ou no acesso a serviços web mais redes lentas).
Haverá um monte de outras razões para fazê-lo embora.
FYI, esta é uma pergunta relacionada sobre cada tipo de re-lance: para lançar exceções
A minha pergunta incide sobre "Por que" nós re-throw exceções e seu uso na estratégia de tratamento de exceção do aplicativo.
Até que eu comecei usando o EntLib ExceptionBlock, eu estava usando-os para registrar erros antes de jogá-los. Tipo de desagradável quando você acha que eu poderia ter lidado com eles naquele momento, mas no momento em que era melhor tê-los falhar nastily em UAT (após log-los) ao invés de cobertura de um fluxo-on bug.
O aplicativo provavelmente vai estar pegando essas exceções jogado re-mais acima na pilha de chamadas e assim re-jogá-los permite que mais acima manipulador para interceptar e processá-las conforme apropriado. É bastante comum para aplicação de ter um manipulador de exceção de nível superior que registros ou relata os expections.
Outra alternativa é que o codificador era preguiçoso e, em vez de pegar apenas o conjunto de exceções que eles querem lidar com eles pegaram tudo e, em seguida, re-lançado apenas os que realmente não pode manipular.
Como Rafal mencionado, por vezes, isso é feito para converter uma exceção verificada para uma exceção não verificada, ou para uma exceção verificada que é mais adequado para uma API. Há um exemplo aqui:
http://radio-weblogs.com/0122027 /stories/2003/04/01/JavasCheckedExceptionsWereAMistake.html
Se você olhar para exceções como em uma maneira alternativa para obter um resultado método , em seguida, re-lançar uma exceção é como embrulhar seu resultado em algum outro objeto.
E esta é uma operação comum em um mundo não-excepcional. Geralmente isso acontece em uma beira de duas camadas de aplicação - quando uma função de B
camada chama uma função de C
camada, ele transforma o resultado de C
em forma interna do B
.
A -- calls --> B -- calls --> C
Se isso não acontecer, então no A
camada que chama a B
camada haverá um conjunto completo de exceções do JDK de manusear. : -)
Como também a resposta aceita aponta, camada A
pode até não estar ciente de exceção de C
.
Layer A , servlet: recupera uma imagem e é meta informação
Layer B , biblioteca JPEG: recolhe etiquetas DCIM disponíveis para analisar um arquivo JPEG
Layer C , um DB simples: uma leitura classe registros de cordas de um arquivo de acesso aleatório. Alguns bytes estão quebrados, por isso lança uma exceção dizendo que "não pode ler string UTF-8 para gravar 'bibliographicCitation'".
Assim A
não vai entender o significado de 'bibliographicCitation'. Então B
deve traduzir essa exceção para A
em TagsReadingException
que envolve o original.
A principal razão de exceções jogando re-é deixar Call Stack intocada, para que possa obter imagem mais completa do que acontece e chama sequência.