Neste artigo vou mostrar algumas boas práticas relacionadas ao tratamento de erros na linguagem C#. |
O
tratamento de erros é fundamental para que sua aplicação seja gerenciada de
forma robusta e consistente. Sem isso você fica a mercê do acaso e não sabe como
proceder quando coisas inesperadas ocorrerem em sua aplicação.
Quando ocorre um erro e seu programa não consegue se recuperar por conta
própria, a forma como você lida com o erro dependerá da quantidade de
informações que o erro fornece sobre o que o causou e onde ocorreu o problema.
Em primeiro lugar, é necessário entender o que significa o
tratamento de erros. O termo tratamento de erros refere-se aos mecanismos de
reação e recuperação de um aplicativo de software no caso de um erro. Ou seja, é
um processo que envolve prever, detectar e resolver com precisão erros em
aplicativos, programas ou comunicação.
Veremos a seguir algumas boas práticas que ajudam no tratamento de erros.
Sempre mantenha o rastreamento da pilha de exceção
Usar throw ex; não é
uma boa maneira de lançar uma exceção capturada, pois o C# nos permite,
simplesmente com o uso de um throw; lançar
a exceção em um bloco catch.
Dessa forma, usando throw, estamos mantendo o controle da pilha e obtendo uma visão muito
melhor da exceção.
Forma Incorreta
try { MetodoQuePodeCausarUmErro(); } catch (Exception ex) { logger.LogInfo(ex); throw ex; } |
Forma correta
try { MetodoQuePodeCausarUmErro(); } catch (Exception error) { logger.LogInfo(error); throw; } |
Incluir informações em uma exceção é uma boa prática, pois ajudará na depuração do erro. Se, por outro lado, o objetivo é registrar uma exceção, então throw deve ser usado para passar a bola para um chamador.
Usando throw ex; não é uma abordagem robusta pois você perde o rastreamento da pilha de exceção. Usando throw; a pilha original da exceção será conservada.
Evite usar if condicionais no bloco try-catch
Você deve considerar o uso de vários blocos catch para tratamento de exceção se precisar executar ações diferentes dependendo do tipo de exceção. Uma má prática é usar um if condicional para realizar esse tratamento:
Forma Incorreta
try { // Código } catch (Exception ex) { if (ex is TaskSchedulerException) { // Tome uma atitude } else if (ex is TaskCanceledException) { // Tome uma atitude } } |
Forma correta
try { // Código } catch (TaskCanceledException ex) { // Tome uma atitude } catch (TaskSchedulerException ex) { // Tome uma atitude } |
Mesmo se estivermos criando vários blocos catch, é sempre sugerido criar um bloco catch que inclua o argumento Exception como o bloco catch final. Ele atua como um bloco de captura auxiliar.
Registre o objeto de exceção ao registrar exceções
Muitas vezes, os desenvolvedores registram apenas a
mensagem de exceção no sistema de registro sem o objeto de exceção. O objeto de
exceção contém informações cruciais, como tipo de exceção, rastreamento de
pilha, etc. e é muito importante registrá-lo.
Se você estiver usando o ILogger, existem métodos
de extensão para LogError, LogCritical, Log, etc
que aceitam o objeto de exceção como parâmetro; use aqueles em vez de apenas os
de mensagem.
Forma Incorreta
... // Código catch (DivideByZeroException ex) { // O objeto ex não esta sendo logado e perdemos informação this.logger.LogError("Uma exceção por divisão por zero ocorreu"); } |
Forma Correta
... // Código catch (DivideByZeroException ex) { // O objeto ex não esta sendo logado e perdemos informação this.logger.LogError(ex, "Uma exceção por divisão por zero ocorreu"); } |
Evite bloco catch que apenas
relance a exceção
Não basta capturar uma exceção e apenas lançá-la novamente. Se o bloco catch não tiver outro propósito e não trata a exceção, remova o bloco try-catch e deixe o método de chamada capturar a exceção e fazer algo com ela.
Forma Incorreta
public void
Metodo1() { try { Metodo2(); } catch (Exception ex) { this.logger.LogError(ex); throw; } } public void Metodo2() |
No exemplo acima, Metodo1 está chamando Metodo2 e existe a possibilidade de ocorrer uma exceção em Metodo2. O bloco catch no Metodo2 não faz nada com exceção e apenas o lança novamente.
Em vez disso, o bloco try-catch em Metodo2 pode ser removido e o chamador que é Metodo1 neste caso pode capturar a exceção e tratá-la.
Forma Correta
public void
Metodo1() { try { Metodo2(); } catch (Exception ex) { //trata a exceção this.logger.LogError(ex); throw; } } public void Metodo2() //try-catch
removido |
Não 'Engula' a exceção
Uma das piores coisas a se fazer no tratamento de exceções é engolir a exceção sem fazer nada.
Se a exceção for engolida sem sequer ser registrada, não haverá nenhum vestígio do problema que ocorreu.
Se você não tiver certeza do que fazer com a exceção, não
a pegue ou pelo menos lance-a novamente.
Forma InCorreta
try { // código que pode lançar uma Exception } catch (Exception ex) { } |
Acima a exceção foi literalmente 'engolida' e não temos nenhum vestígio
do que pode ter ocorrido nestes casos. Aqui, embora não seja ideal, como já
mostramos, lançar a exceção traria menos prejuízo.
Em quase todas as situações em que você coloca um bloco
try/catch em torno de um bloco de código, você deve
sempre ter um manipulador de captura apropriado que capture uma exceção
específica como (UnAuthorizedAccessException ex) - ou mesmo uma exceção
não específica como (Exception ex) para transmitir ou manipular a exceção
no local apropriado.
É melhor lidar com exceções o mais próximo possível da fonte, porque quanto mais
próximo você estiver, mais contexto terá para fazer algo útil com a exceção.
Lance a exceção ao invés de retornar um código de erro
Às vezes, em vez de lançar exceções, os desenvolvedores retornam códigos de erro
para o método de chamada, isso pode fazer com que as exceções passem
despercebidas, pois os métodos de chamada nem sempre verificam o código de
retorno.
Nota: Naturalmente em um ambiente de uma Web API temos os código de status ou Status Code que são usados para retornar o código do status indicando se um request HTTP foi concluído corretamente.
forma Incorreta
public int
Method2() { try { // código que pode lançar uma Exception } catch (Exception) { LogError(ex); //não verifica a exceção return -1; } } |
Nesta abordagem você terá que definir uma lógica para verificar o código do erro retornado e lançar exceções possuem as seguintes vantagens sobre esta abordagem:
As exceções deixam seu código limpo de todas as verificações necessárias;
As exceções permitem que você use o valor de retorno de funções para valores reais;
As exceções podem carregar mais informações do que um retorno de status;
As exceções não podem ser ignoradas por inação, enquanto os retornos de status podem;
Estas são algumas práticas de tratamento de exceções que podem ser muito úteis no meu trabalho diário. Algumas podem parecer óbvias, mas muitas vezes passam despercebidos por muitos.
E estamos conversados...
"Sei estar abatido, e sei também ter abundância; em
toda a maneira, e em todas as coisas estou instruído, tanto a ter fartura, como
a ter fome; tanto a ter abundância, como a padecer necessidade.
Posso todas as coisas em Cristo que me fortalece."
Filipenses 4:12,13
Referências: