C# - Boas práticas no tratamento de erros
 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()
{
    try
    {
        //Código que lança a exceção
    }
    catch (Exception)
    {
        throw;
    }

}

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()
{
    //Código que lança a exceção

   //try-catch removido
   //pois o método não trata a exceção

}

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:

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

Porque um menino nos nasceu, um filho se nos deu, e o principado está sobre os seus ombros, e se chamará o seu nome: Maravilhoso, Conselheiro, Deus Forte, Pai da Eternidade, Príncipe da Paz.

Isaías 9:6
Porque um menino nos nasceu, um filho se nos deu, e o principado está sobre os seus ombros, e se chamará o seu nome: Maravilhoso, Conselheiro, Deus Forte, Pai da Eternidade, Príncipe da Paz.

Isaías 9:6

Referências:


José Carlos Macoratti