Qu'est-ce qu'une erreur ?
Les erreurs sont des problèmes dans le code. Il peut s'agir de fautes de frappe, ou encore de variables et fonctions mal utilisées. Il est parfois possible de les repérer immédiatement, mais il arrive aussi qu'elles n'apparaissent que pendant l'exécution du programme. Elles dépendent souvent de la séquence d'exécution et des résultats du code précédemment exécuté.
Reprenons le processus de création et d'utilisation d'une application. Tout d'abord, vous écrivez du code dans un langage de programmation. Ce code est ensuite traduit en code machine avant d'être exécuté lorsque vous utilisez l'application.
Voici les deux phases de ce processus :
compilation (ou interprétation, dans certains langages) : c'est à ce moment que le code est vérifié et traduit en code machine. Le résultat du processus de compilation est un fichier qui est prêt à être exécuté et appelé exécutable.
exécution : c'est la phase durant laquelle l'application ou le fichier exécutable est lancé !
Chacune de ces phases peut avoir son propre ensemble d'erreurs.
Repérez et corrigez les erreurs de compilation
Les erreurs de compilation peuvent être détectées par le compilateur C# (csc.exe), pendant le processus de traduction de votre code C# lisible par un être humain en bytecode lisible par une machine. Ces erreurs peuvent être syntaxiques (fautes d'orthographe). Elles peuvent aussi être sémantiques, telles que l'utilisation de mots interdits, de code non interprétable, de types incorrects, etc.
Si vous utilisez un environnement de développement intégré (IDE) comme Visual Studio, vous serez averti en temps réel des erreurs lorsque vous taperez votre code. Les lignes de code qui présentent des erreurs de compilation sont surlignées. Ce sont des erreurs dites « faciles », car elles peuvent (et doivent) être corrigées immédiatement. L'application ne peut pas être compilée tant qu'il reste des erreurs de compilation. Pour autant, elles sont faciles à corriger, et vous pouvez voir exactement ce qui ne va pas.
Les erreurs d'exécution peuvent, quant à elles, être plus difficiles à corriger.
Gérez les erreurs d'exécution
Les erreurs d'exécution surviennent pendant le processus d'exécution, après le lancement de l'application. Elles n'apparaissent que pendant le processus d'exécution, et il n'y a aucun moyen de les détecter pendant la compilation.
Ce type d'erreur est typiquement lié à la logique du code, comme l'accès à des données qui n'existent pas ou qui existent dans un format différent, l'exécution d'une action non prise en charge, etc.
Par exemple, si vous essayez d'accéder à un élément d'une liste dont l'index est supérieur à la longueur de cette liste, une erreur de logique de code survient. Vous pensiez que la liste serait plus longue.
Un exemple d'erreur de logique métier pourrait être que dans une application financière, vous avez inversé la signification des termes crédit et débit. Évidemment, vous n'obtiendrez pas les résultats escomptés, mais l'application parviendra quand même à fonctionner. Elle va simplement produire tout le contraire du résultat que vous attendiez ! Ne vous trompez pas ! 😉
En général, une erreur logique a pour principale conséquence un plantage de l'application.
Certaines de ces erreurs peuvent être facilement reproduites, car elles surviennent régulièrement. D'autres ne se produisent qu'occasionnellement. Elles sont plus difficiles à résoudre, car elles sont généralement plus difficiles à reproduire. Les erreurs occasionnelles sont habituellement liées à des conditions particulières du code. Voici un exemple de ce genre d'erreur ninja dans la structure du code :
if (jourSemaine)
{
// Exécuter le code sans erreur
}
else
{
// Exécuter le code avec des erreurs
}
Dans l'exemple ci-dessus, si vous ne travaillez sur votre projet que du lundi au vendredi et que tous les plantages surviennent pendant le week-end, il ne sera pas évident au départ de savoir quelle en est la cause.
Alors, qu'est-ce que vous pouvez faire ?
Dans certains cas, vous pouvez déboguer votre programme et détecter ces points faibles.
Dans d’autres cas, vous pouvez soupçonner que certaines parties du code pourraient poser problème, par exemple en raison de dépendances externes (parties d’un périphérique, autres applications, stockage externe, réseau, etc.).
Pour y remédier, vous pouvez travailler en adaptant votre programme à ces contraintes (et non l'inverse 🙂). Vous pouvez généralement utiliser deux stratégies :
Rechercher des erreurs à l'aide de conditions.
Tirer parti du mécanisme des exceptions.
Étudions chaque stratégie avec un exemple courant : la fameuse erreur de la division par zéro.
Recherchez les erreurs
Créez d'abord une classe utilitaire dans un espace de noms Exceptions
, appelée MathSimple
, qui fournit une méthode CalculMoyenne
permettant de calculer la moyenne d'une liste de valeurs entières.
using System;
using System.Collections.Generic;
namespace Exceptions
{
public class MathSimple
{
/// <summary>
/// Calculer la valeur moyenne d'une liste d'entiers
/// </summary>
/// <param name="listeDesEntiers">Une liste contenant des nombres entiers</param>
/// <returns>La moyenne de la liste</returns>
public static int CalculMoyenne(List<int> listeDesEntiers)
{
int moyenne= 0;
foreach (int valeur in listeDesEntiers)
{
moyenne += valeur;
}
moyenne /= listeDesEntiers.Count;
return moyenne;
}
}
}
Créez ensuite un programme dans une classe nommée MoyenneTemperatures
dans le même espace de noms Exceptions qui fait usage de cette fonctionnalité. Ce programme reçoit les valeurs de température comme arguments en ligne de commande. Il appelle la fonction CalculMoyenne
et affiche le résultat.
using System;
using System.Collections.Generic;
namespace Exceptions
{
public class MoyenneTemperatures
{
/// <summary>
/// Afficher la température moyenne à partir des valeurs fournies comme arguments en ligne de commande
/// </summary>
/// <param name="args">Liste de températures séparées par des espaces</param>
public static void Main(string[] args)
{
List<int> temperaturesEnregistreesDegresCelcius = new List<int>();
// Remplir la liste à partir des valeurs fournies comme arguments en ligne de commande
foreach(string stringRepresentationTemperature in args)
{
int temperature = int.Parse(stringRepresentationTemperature);
temperaturesEnregistreesDegresCelcius.Add(temperature);
}
// Calculer et afficher la température moyenne
int temperatureMoyenne =
MathSimple.CalculMoyenne(temperaturesEnregistreesDegresCelcius);
Console.WriteLine("La température moyenne est " + temperatureMoyenne);
}
}
}
Dans ce code, vous créez d'abord une liste vide, puis itérez sur l'array args contenant les arguments de ligne de commande fournis. Puisque chaque argument est fourni sous la forme d'une chaîne de caractères ( string
), convertissez-le en int
à l'aide de la méthode utilitaire int.Parse avant de l'ajouter à la liste. Une fois la liste complétée, appelez la fonction CalculMoyenne
de la classe MathSimple
, affectez le résultat à la variable temperatureMoyenne
et affichez sa valeur.
Testez par vous-même !
Prêt à coder ? Pour accéder à l’exercice, suivez ce lien.
Sans surprise, vous rencontrez un problème si vous ne fournissez aucun argument sur la ligne de commande, puisque vous ne pouvez pas diviser par zéro. Pour éviter cette erreur, ajoutez un contrôle dans la méthode CalculMoyenne
:
if (listeDesEntiers.Count == 0)
{
return 0;
}
Toutefois, cette stratégie produirait un résultat très trompeur : 0
n'est pas un résultat valide. Nous n'avons même pas effectué la division ! Nous pouvons, cependant, améliorer la fonction Main
pour éviter d'appeler la fonction avec une liste vide :
using System;
using System.Collections.Generic;
namespace Exceptions
{
public class MoyenneTemperaturesCorrigee
{
/// <summary>
/// Afficher la température moyenne à partir des valeurs fournies comme arguments en ligne de commande
/// </summary>
/// <param name="args">Liste de températures séparées par des espaces</param>
public static void Main(string[] args)
{
List<int> temperaturesEnregistreesDegresCelcius = new List<int>();
// Remplir la liste à partir des valeurs fournies comme arguments de ligne de commande
foreach(string stringRepresentationTemperature in args)
{
int temperature = int.Parse(stringRepresentationTemperature);
temperaturesEnregistreesDegresCelcius.Add(temperature);
}
// Gérer une liste vide
if(temperaturesEnregistreesDegresCelcius.Count == 0)
{
Console.WriteLine("Impossible de calculer la moyenne avec une liste vide !");
}
else
{
// Calculer et afficher la température moyenne
int temperatureMoyenne =
MathSimple.CalculMoyenne(temperaturesEnregistreesDegresCelcius);
Console.WriteLine("La température moyenne est " + temperatureMoyenne);
}
}
}
}
Testez par vous-même !
Prêt à coder ? Pour accéder à l’exercice, suivez ce lien.
Il semble qu'il y ait un autre problème : notre méthode int.Parse
n'accepte pas « huit » comme entrée, et lève une exception NumberFormatException qui fait planter le programme.
Intéressant ! Nous obtenons ce qu'on appelle une trace d'appel : un message que C# affiche lorsqu'un programme plante. Il nous explique même que ce plantage est dû à une erreur de format de la chaîne d'entrée (System.FormatException: Input string was not in correct format).
Donc, si C# est capable d'afficher ce message, pourrait-il empêcher que le programme plante ?
Bien sûr que oui ! C'est à cela que sert le mécanisme de gestion des exceptions.🙂
Gérez les exceptions
Le mécanisme de gestion des exceptions est le mécanisme de gestion des erreurs inclus par défaut dans C#. Il a pour fonction de lever une exception avec un événement qui interrompt le déroulement normal de l'exécution en cas de problème. Si cet événement est détecté, le problème peut être résolu. Sinon, le programme plante avec une trace d'appel qui décrit ce qui s'est passé.
Pour utiliser ce mécanisme, il convient de suivre un modèle de code général, try/catch
. Cela signifie que vous demandez à exécuter un bloc de code, ou plutôt à essayer ( try
) de le faire. Si une erreur se produit, vous pourrez l'intercepter ( catch
).
Vous pouvez gérer l'erreur (ou les erreurs) dans la partie catch. Cela peut être aussi simple que de ne rien faire ou de réessayer (si votre logique métier le permet). En interceptant une erreur (même si vous ne faites rien de plus), vous empêchez l'application de planter.
Voici à quoi ressemble cette construction en C# :
try
{
// Un peu de code
// Une fonction à essayer pouvant générer une erreur
// Encore du code
}
catch (ExceptionName e) {
// Code à exécuter au cas où l'essai ne fonctionnerait pas et qu'une erreur se produirait
}
Que se passe-t-il au moment de l'exécution du bloc try ?
En cas de problème à l'intérieur d'une méthode pendant son exécution, une erreur est générée (on dit qu'une exception est levée). Cette exception remonte la chaîne d'appels de méthode jusqu'à ce qu'elle soit interceptée. Si aucune instruction catch n'est fournie, le programme finit par planter.
Corrigeons notre programme pour gérer nos sources d'erreurs connues :
using System;
using System.Collections.Generic;
namespace Exceptions
{
public class MoyenneTemperaturesAvecGestionExceptions
{
/// <summary>
/// Afficher la température moyenne à partir des valeurs fournies comme arguments en ligne de commande
/// </summary>
/// <param name="args">Liste de températures séparées par des espaces</param>
public static void Main(string[] args)
{
try
{
List<int> temperaturesEnregistreesDegresCelcius = new List<int>();
// Remplir la liste à partir des valeurs fournies comme arguments en ligne de commande
foreach(string stringRepresentationTemperature in args)
{
int temperature = int.Parse(stringRepresentationTemperature);
temperaturesEnregistreesDegresCelcius.Add(temperature);
}
// Calculer et afficher la température moyenne
int temperatureMoyenne =
MathSimple.CalculMoyenne(temperaturesEnregistreesDegresCelcius);
Console.WriteLine("La température moyenne est de " + temperatureMoyenne);
}
catch (FormatException e)
{
Console.WriteLine("Les températures fournies doivent être des nombres");
Environment.Exit(-1);
}
catch (DivideByZeroException e)
{
Console.WriteLine("Vous devez fournir au moins une température");
Environment.Exit(-1);
}
}
}
}
Ce code est divisé en deux parties :
Le déroulement normal, entre
try {
et}
: il se compose des instructions à exécuter tant qu'aucune erreur ne se produit.Un ensemble d'instructions
catch(Exception e)
: si une erreur se produit et que l'exception qui est levée correspond au type d'exception défini dans une instructioncatch
, le bloc qui correspond à cette instruction catch est exécuté. Si aucune instruction catch ne correspond, le programme plante et affiche la trace d'appel.
Ce mécanisme permet de séparer le déroulement normal d'un programme de la partie de traitement des erreurs. Il permet même de gérer une erreur de méthode dans la méthode d'appel. Dans notre exemple, l'exception DivideByZeroException
est générée depuis la méthode CalculMoyenne
, mais interceptée dans la méthode Main
.
Testez par vous-même !
Prêt à coder ? Pour accéder à l’exercice, suivez ce lien.
En résumé
Dans ce chapitre, vous avez appris des notions sur la gestion des erreurs :
La compilation est le processus de traduction du code d'un langage de programmation en code machine. Les erreurs de compilation sont faciles à détecter et doivent être résolues pour terminer le processus de compilation.
L'exécution est le processus d'utilisation de l'application. Les erreurs d'exécution sont plus difficiles à détecter et font planter une application.
En C#, les erreurs d'exécution génèrent des exceptions.
Les exceptions sont gérées à l'aide d'une instruction try/catch :
Try indique qu'une fonction peut lancer une exception.
Catch indique quel code exécuter si une exception est générée.
Dans le chapitre suivant, nous traiterons des interactions avec un utilisateur. Nous pourrons alors approfondir la gestion des exceptions suivant les cas que nous allons rencontrer.