Gestion des exceptions en C#

En faisant du code review chez un client j'ai retrouvé quelques erreurs qu'il ne faut absolument pas faire pour traiter les exceptions.

Erreur #1: catcher le type Exception en dehors du global handler

public void MaFonction()
{
  try
  {
    // votre fonction
  }
  catch (Exception ex)
  {
    // Log & throw
  }
}

Le bloc catch(Exception ex) ne doit apparaitre qu'à un seul endroit de manière générale et c'est dans le global handler.

Plus les blocs catch sont près du code qui renvoie l'exception plus l'exception attrapée doit être le plus fortement typé possible.

Erreur #2: catcher et avaler une exception pour éviter que l'application plante

public void MaFonction()
{
  try
  {
    // votre fonction
  }
  catch (Exception ex)
  {
    // Log uniquement et pas de throw
  }
}

Cette erreur pose plusieurs problèmes, elle masque une vraie erreur et rend le débogage plus difficile car l'application ne plante pas.

L'objectif d'une exception est de vous informer qu'il y a un cas imprévu qui a été levé et vous en informe. Laissez l'application s'exprimer, elle doit vous dire où elle a mal.

Certaines exceptions peuvent être avalées, mais elles doivent être fortement typées et attendues.

Erreur #3: écrasement de la stacktrace

Nous avons le petit programme suivant qui appelle une méthode de ClassA qui appelle une méthode de ClassB qui renvoie une exception.

class Program
{
  static void Main(string[] args)
  {
      try
      {
        ClassA.MaFonction();
      }
      catch(Exception ex)
      {
        Console.WriteLine(ex.StackTrace);
      }
  }
}

public static class ClassA
{
  public static void MaFonction()
  {
    try
    {
      ClassB.Fail();
    }
    catch (Exception ex)
    {
      // Cas #1
      throw;
      // Cas #2
      throw ex;
    }
  }
}

public static class ClassB
{
  public static void Fail()
  {
    throw new Exception();
  }
}

Si on observe le cas 1 avec juste throw, la stacktrace affichée contient l'endroit où l'exception a été levée. Ceci est la bonne méthode.

at ConsoleApp1.ClassB.Fail() in c:\dev\ConsoleApp1\ConsoleApp1\Program.cs:line 39
at ConsoleApp1.ClassA.MaFonction() in c:\dev\ConsoleApp1\ConsoleApp1\Program.cs:line 30
at ConsoleApp1.Program.Main(String[] args) in c:\dev\ConsoleApp1\ConsoleApp1\Program.cs:line 11

Par contre si on prend le cas 2 throw ex, la stacktrace a été écrasée et vous perdez l'origine de l'erreur. A ne pas faire.

at ConsoleApp1.ClassA.MaFonction() in c:\dev\ConsoleApp1\ConsoleApp1\Program.cs:line 30
at ConsoleApp1.Program.Main(String[] args) in c:\dev\ConsoleApp1\ConsoleApp1\Program.cs:line 11

Le sujet du traitement des exceptions est très vaste, beaucoup de choses peuvent se discuter, mais globalement, je vous conseille de toujours écrire un global handler au début de votre projet et d'affiner les blocs try/catch au plus proche des exceptions qui vous intéressent.

Si vous ne pouvez rien faire avec une exception, laissez-la remonter jusqu'au global handler, vous éviterez ainsi d'alourdir votre code base inutilement et ça sera plus agréable à relire dans le temps.