Masquage d'erreur.
Ne pas signaler les détails d'une erreur
En programmation informatique, le masquage d'erreur (ou absorption d'erreur) est la pratique consistant à intercepter une erreur ou une exception, puis à poursuivre sans journaliser, traiter ni signaler l'erreur aux autres parties du logiciel. Gérer les erreurs de cette manière est considéré comme une mauvaise pratique et comme un anti-pattern en programmation informatique. Dans les langages dotés de la prise en charge de la gestion des exceptions, cette pratique est appelée absorption d'exception.
Les erreurs et les exceptions ont plusieurs finalités :
- Aider les mainteneurs du logiciel à localiser et à comprendre les problèmes survenant lorsqu'un utilisateur exécute le logiciel, lorsqu'elles sont associées à un système de journalisation
- Fournir des informations utiles à l'utilisateur du logiciel, lorsqu'elles sont associées à des messages d'erreur explicites, à des codes d'erreur ou à des types d'erreur affichés dans une interface utilisateur, sous forme de messages de console ou de données renvoyées par une API (selon le type de logiciel et le type d'utilisateur)
- Indiquer que le fonctionnement normal ne peut pas se poursuivre, afin que le logiciel puisse recourir à d'autres moyens d'accomplir la tâche requise ou interrompre l'opération.
Lorsque les erreurs sont absorbées, ces finalités ne peuvent être atteintes. Les informations relatives à l'erreur sont perdues, ce qui rend très difficile la localisation des problèmes. Selon la manière dont le logiciel est implémenté, cela peut provoquer des effets de bord involontaires qui se propagent en cascade vers d'autres erreurs, déstabilisant le système. Sans information sur la cause profonde du problème, il est très difficile de comprendre ce qui ne va pas ou comment le corriger.
Exemples
Langages avec gestion des exceptions
Dans cet exemple en C#, bien que le code situé à l'intérieur du bloc try lève une exception, celle-ci est interceptée par la clause catch générale. L'exception a été absorbée et est considérée comme traitée, et le programme se poursuit.
try {
throw new Exception();
} catch {
// ne rien faire
}
Dans cet exemple en PowerShell, la clause trap intercepte l'exception levée et l'absorbe en poursuivant l'exécution. Le message "I should not be here" est affiché comme si aucune exception ne s'était produite.
&{
trap { continue }
throw
write-output "I should not be here"
}
L'absorption d'exception peut aussi se produire si l'exception est traitée puis relancée sous la forme d'une exception différente, écartant l'exception d'origine et tout son contexte.
Dans cet exemple en C#, toutes les exceptions sont interceptées quel que soit leur type, et une nouvelle exception générique est levée, ne conservant que le message de l'exception d'origine. La trace de pile d'origine est perdue, de même que le type de l'exception d'origine, toute exception que l'exception d'origine encapsulait, et toute autre information capturée dans l'objet exception.
try {
// faire quelque chose
} catch (Exception ex) {
// éventuellement effectuer un traitement local de l'exception
throw new Exception(ex.Message);
}
Une meilleure façon de relancer des exceptions sans perdre d'information consiste à relancer l'exception d'origine depuis la clause catch :
try {
// faire quelque chose
} catch (Exception ex) {
// effectuer un traitement local de l'exception
throw;
}
Une autre solution consiste à créer une nouvelle exception qui encapsule l'exception d'origine, de sorte que tout autre gestionnaire ait accès aux deux :
try {
// faire quelque chose
} catch(Exception ex) {
// éventuellement effectuer un traitement local de l'exception
throw new CustomException(ex);
}
Autres langages
En Go, les erreurs sont propagées en renvoyant un objet Error en plus de la valeur de retour normale de la fonction. Elle peut être ignorée, comme dans cet exemple.
f, _ := "I should not be here", errors.New("")
fmt.Print(f)
Dans le cas des appels système en C, les erreurs sont signalées par une valeur de retour de l'appel égale à NULL, et les informations d'erreur sont stockées dans une variable globale errno. Ce code s'assure que le file est valide avant d'y accéder, mais si fopen a échoué, l'erreur est absorbée.
FILE *file = fopen("", "r");
if (file) {
// faire quelque chose avec le fichier
}
Causes
La cause sous-jacente la plus fréquente de l'absorption d'erreur est l'absence de bons outils et processus de journalisation pendant que le développeur construit le logiciel. Face à une erreur qui ne peut être facilement traitée, si le développeur dispose de bons outils de journalisation, consigner une erreur inattendue ne lui coûte ni temps ni effort. La journalisation de l'erreur devrait être simple (un seul appel de méthode), rapide (sans incidence sur les performances de l'application), sûre (sans lever d'erreur ni d'exception), et garantir que toutes les informations sont sauvegardées, en enregistrant le type d'erreur et toutes les données pertinentes qui y sont associées, la trace de pile de l'erreur (afin que le développeur puisse identifier exactement où l'erreur s'est produite et quelles instructions y ont conduit), ainsi que l'horodatage de l'erreur.
Gestionnaires d'exceptions temporaires
Dans les langages dotés d'exceptions vérifiées, toutes les exceptions levées dans une méthode doivent figurer dans la signature de cette méthode. Lors du prototypage et de l'implémentation d'un logiciel, le code change souvent, ce qui signifie que le type des exceptions susceptibles d'être levées dans une méthode change aussi souvent. Devoir ajuster la signature de la méthode chaque fois qu'un changement survient ralentit le développement et peut être frustrant ; absorber les exceptions à titre de mesure temporaire pendant d'importantes modifications de code est donc tentant. Ce code de gestion d'exceptions temporaire peut se retrouver dans la base de code livrée.
Même dans les langages sans exceptions vérifiées, il peut arriver que l'on ajoute des gestionnaires d'exceptions temporaires pendant d'importantes modifications de code afin d'accélérer le prototypage, ce qui peut conduire à l'absorption d'erreur.
Éviter les plantages
Dans les situations où un logiciel ne doit planter sous aucun prétexte, l'absorption d'erreur est une pratique dans laquelle un programmeur peut facilement tomber. Par exemple, un plugin qui s'exécute à l'intérieur d'une autre application est censé gérer toutes les erreurs et exceptions de manière à ne pas faire planter l'application dans laquelle il est intégré. L'interception générale des erreurs et des exceptions est un schéma dans lequel il est facile de tomber lorsqu'on cherche à éviter les plantages à tout prix, et lorsqu'on combine cela avec de mauvais outils de journalisation, l'absorption d'erreur peut survenir.
Masquer la complexité aux utilisateurs
Lorsqu'on présente des erreurs aux utilisateurs, il est important de transformer des erreurs techniques absconses en messages expliquant ce qui s'est passé et quelles actions l'utilisateur peut entreprendre, le cas échéant, pour résoudre le problème. Au cours de cette traduction des erreurs techniques en messages utiles pour l'utilisateur, des erreurs spécifiques sont souvent regroupées en erreurs plus génériques, et ce processus peut conduire à des messages utilisateur devenant si inutiles que l'utilisateur ne sait ni ce qui n'a pas fonctionné ni comment y remédier. Du point de vue de l'utilisateur, l'erreur a été absorbée.