C# - Tratamento de exceções : boas práticas
 Vamos recordar como realizar o tratamento de exceções usando boas práticas.

O tratamento de exceções é de fundamental importância no desenvolvimento de software e por isso deve ser feito da forma correta.

Veremos a seguir boas práticas que podemos usar para melhorar esse fundamento em nosso código.

1- Não relance exceções

Eu já escrevi sobre isso mais de uma vez, mas vale a pena reforçar o conceito de que não devemos re-lançar exceptions em nosso código.

Ao fazer isso o rastreamento da pilha original vai desaparecer no tratamento de erros e assim não teremos como saber qual a exceção original.

Vejamos um exemplo onde temos um try/catch aninhado :

Neste trecho de código o try/catch interno captura, registra e 'engole' a exceção.

Para lançar a exceção EspecificExeception para o bloco try/catch global e realizar o tratamento precisamos lançar a exceção na pilha e podemos fazer isso de duas formas:

a- usando um throw specificException

Nesta abordagem o stack trace da exceção original será resetado e perdido.

b- usando apenas um throw

Neste código estamos retendo todos os detalhes da exceção original e é a abordagem mais recomendada a ser usada.

2- Decorando exceptions com mais informações

Todas as exceções estendem Exception, que possui um dicionário Data.

Este dicionário Data ser usado para incluir informações adicionais sobre um erro. A visibilidade ou não dessas informações em seu log depende de qual framework de log e armazenamento você está usando.

Para incluir informação no Dicionário Data podemos incluir um novo par chave/valor (key/value):

var exception = new Exception("Ocorreu um erro");

exception.Data.Add("user", Thread.CurrentPrincipal.Identity.Name);

throw exception;

Exemplo de uso em um bloco try/catch:

try
{
    Servico.Registrar();
}
catch (Exception e)
{
    e.Data.Add("user", Thread.CurrentPrincipal.Identity.Name);
    throw;
}

O código captura todas as exceções lançadas pelo método Registrar() e inclui um nome de usuário na exceção. Ao adicionar a palavra-chave throw ao bloco catch, a exceção original é lançada mais acima na pilha.

3- Capturando exceções mais especificas primeiro

Considere o seguinte trecho de código:

Simplesmente capturar Exception e registrá-la em sua estrutura de log preferida é rápido de implementar e funciona.

A maioria das bibliotecas disponíveis no .NET pode lançar uma variedade de exceções diferentes e podemos refinar o tratamento de erros capturando várias exceções, desde o erro mais específico até o menos específico, e, isso é uma ótima maneira de diferenciar como você deseja continuar em cada tipo.

No exemplo a seguir estamos explicitando o tratamento de exceções definindo quais exceções esperar e como tratar cada tipo de exceção :

Ao capturar ArgumentException e DirectoryNotFoundException antes de capturar a exceção genérica, podemos mostrar uma mensagem especializada ao usuário.

Nesses cenários, não registramos a exceção, pois o usuário pode corrigir os erros rapidamente. No caso de uma Exception, queremos gerar um ticket de suporte, registrando o erro (para isso usamos decoradores conforme mostrado anteriormente) e exibimos uma mensagem para o usuário.

Observe que, embora o código acima sirva ao nosso propósito de explicar a ordem das exceções, é uma prática ruim implementar o fluxo de controle usando exceções como esta. A melhor abordagem é evitar exceções como veremos a seguir.

4- Evite Exceptions

Como assim evitar Exceptions ???

Embora o uso de manipuladores de exceção para detectar erros e outros eventos que interrompem a execução do programa seja uma boa prática, o uso do manipulador de exceção como parte da lógica de execução regular do programa pode ser caro e deve ser evitado.

Na maioria dos casos, as exceções devem ser usadas apenas para circunstâncias que ocorrem com pouca frequência e não são esperadas.

As exceções não devem ser usadas para retornar valores como parte do fluxo de programa típico. Em muitos casos, você pode evitar gerar exceções validando valores e usando lógica condicional para interromper a execução de instruções que causam o problema.

Como exemplo temos que muitos métodos que lançam uma exceção podem ser evitados pela programação defensiva.

Uma das exceções mais comuns é NullReferenceException. Em alguns casos, você pode querer permitir o uso do null, mas esquece de verificar se existe null.

Aqui está um exemplo que lança uma NullReferenceException:

Caso você queira permitir uma cidade com valor nulo, você pode evitar a exceção usando o operador condicional nulo:

Ao anexar ? ao acessar a, o C# manipula automaticamente o cenário em que o endereço é nulo. Nesse caso, a variável cidade obterá o valor null.

Outro exemplo comum de exceções é ao analisar números ou booleanos. O exemplo a seguir lançará um FormatException:


   var i = int.Parse("inválido");       
 

A string 'inválido' não pode ser analisada como um inteiro. Em vez de incluir um try/catch, o tipo int fornece um método sofisticado que você provavelmente já usou :

Agora,  caso 'inválido' possa ser analisado como um int, o TryParse retorna true e coloca o valor analisado na variável i.

Outra exceção evitada.

5- Crie exceções customizadas

A Plataforma .NET fornece a classe System.Exception que é a classe base para todas as exceções.

Para criar sua exceção personalizada tudo o que você precisa fazer é criar uma classe que herde da classe Exception ou da classe Application.Exception.

Ao criar uma exceção personalizada, você tem possibilidades muito melhores de capturar exceções específicas, como já mostrado. Você pode decorar sua exceção com variáveis personalizadas sem ter que se preocupar se o seu logger suporta o dicionário de dados.

Exemplo:

public class MinhaExcecaoEspecializada : Exception
{
    public MinhaExcecaoEspecializada() : base() { }
    public MinhaExcecaoEspecializada(string message) : base(message) { }
    public MinhaExcecaoEspecializada(string message, Exception inner) : base(message, inner) { }
    public int Status { get; set; }
}

A classe MinhaExcecaoEspecializada implementa três construtores que toda classe de exceção deve ter. Além disso, incluímos uma propriedade Status como exemplo de dados adicionais. Isso tornará possível escrever código como este:

Usando a palavra-chave when, posso capturar uma MinhaExcecaoEspecializada quando o valor da propriedade Status for 500. Todos os outros cenários terminarão na captura geral de MinhaExcecaoEspecializada.

Essas são algumas recomendações de boas práticas para realizar um tratamento de exceções mais robusto.

E estamos conversados.

"Celebrai com júbilo ao SENHOR, todas as terras.
Servi ao Senhor com alegria; e entrai diante dele com canto. "
Salmos 101:1-2

Referências:


José Carlos Macoratti