En tant que bon programmeur, il faut écrire des codes robustes qui gèrent chaque résultat de son programme. Cependant, presque toutes les fonctions de la bibliothèque C renverront 0 ou -1 ou NULL en cas d'erreur.
Il est parfois évident que la vérification des erreurs est nécessaire, par exemple lorsque vous essayez d'ouvrir un fichier. Mais j'ignore souvent la vérification des erreurs dans des fonctions telles que printf
ou même malloc
parce que je ne me sens pas nécessaire.
if(fprintf(stderr, "%s", errMsg) < 0){
perror("An error occurred while displaying the previous error.");
exit(1);
}
Est-ce une bonne pratique de simplement ignorer certaines erreurs ou existe-t-il une meilleure façon de gérer toutes les erreurs?
En général, le code devrait traiter des conditions exceptionnelles partout où cela est approprié. Oui, c'est une déclaration vague.
Dans les langages de niveau supérieur avec gestion des exceptions logicielles, cela est souvent indiqué comme "intercepter l'exception dans la méthode où vous pouvez réellement faire quelque chose". Si une erreur de fichier s'est produite, vous pouvez peut-être la laisser remonter la pile vers le code de l'interface utilisateur qui peut réellement dire à l'utilisateur "votre fichier n'a pas pu être enregistré sur le disque". Le mécanisme d'exception engloutit efficacement "chaque petite erreur" et la gère implicitement à l'endroit approprié.
En C, vous n'avez pas ce luxe. Il existe plusieurs façons de gérer les erreurs, dont certaines sont des fonctionnalités de langue/bibliothèque, dont certaines sont des pratiques de codage.
Est-ce une bonne pratique de simplement ignorer certaines erreurs ou existe-t-il une meilleure façon de gérer toutes les erreurs?
Ignorer certaines erreurs? Peut être. Par exemple, il est raisonnable de supposer que l'écriture sur la sortie standard n'échouera pas. Si cela échoue, comment diriez-vous à l'utilisateur, de toute façon? Oui, c'est une bonne idée d'ignorer certaines erreurs ou de coder de manière défensive pour les éviter. Par exemple, vérifiez zéro avant de diviser.
Il existe des moyens de gérer toutes les erreurs, ou au moins la plupart:
if
s en cascade:
if (!<something>) {
printf("oh no 1!");
return;
}
if (!<something else>) {
printf("oh no 2!");
return;
}
Testez la première condition, par ex. ouvrir ou créer un fichier, puis supposer que les opérations suivantes réussissent.
Un code robuste est bon, et il faut vérifier et gérer les erreurs. La méthode qui convient le mieux à votre code dépend de ce que fait le code, de la gravité d'une défaillance, etc. et vous seul pouvez vraiment y répondre. Cependant, ces méthodes sont testées au combat et utilisées dans divers projets open source où vous pouvez jeter un œil pour voir comment le code réel vérifie les erreurs.
Cependant, presque toutes les fonctions de la bibliothèque C renverront 0 ou -1 ou NULL en cas d'erreur.
Oui, mais vous savez quelle fonction vous avez appelée, n'est-ce pas?
Vous avez en fait beaucoup d'informations que vous pourriez mettre dans un message d'erreur. Vous savez quelle fonction a été appelée, le nom de la fonction qui l'a appelée, quels paramètres ont été passés et la valeur de retour de la fonction. C'est beaucoup d'informations pour un message d'erreur très informatif.
Vous n'avez pas à le faire pour chaque appel de fonction. Mais la première fois que vous voyez le message d'erreur "Une erreur s'est produite lors de l'affichage de l'erreur précédente", alors que ce dont vous aviez vraiment besoin était des informations utiles, ce sera la dernière fois que vous verrez ce message d'erreur, car vous allez immédiatement changer le message d'erreur à quelque chose d'information qui vous aidera à résoudre le problème.
TLDR; vous ne devriez presque jamais ignorer les erreurs.
Le langage C manque d'une bonne fonction de gestion des erreurs, laissant à chaque développeur de bibliothèque la possibilité d'implémenter ses propres solutions. Des langues plus modernes ont des exceptions intégrées, ce qui rend ce problème particulier beaucoup plus facile à gérer.
Mais quand vous êtes coincé avec C, vous n'avez pas de tels avantages. Malheureusement, vous devrez simplement payer le prix chaque fois que vous appelez une fonction qui présente une possibilité d'échec à distance. Sinon, vous subirez des conséquences bien pires, comme l'écrasement involontaire de données en mémoire. Donc, en règle générale, vous devez toujours vérifier les erreurs.
Si vous ne vérifiez pas le retour de fprintf
, vous laisserez très probablement un bogue qui, dans le meilleur des cas, ne fera pas ce que l'utilisateur attend et le pire des cas explosera le tout pendant le vol. Il n'y a aucune excuse pour vous miner de cette façon.
Cependant, en tant que développeur C, il est également de votre devoir de rendre le code facile à maintenir. Donc, parfois, vous pouvez ignorer les erreurs en toute sécurité pour des raisons de clarté si (et seulement si) elles ne constituent pas une menace pour le comportement global de l'application..
C'est le même problème que de faire ceci:
try
{
run();
} catch (Exception) {
// comments expected here!!!
}
Si vous voyez que sans bons commentaires à l'intérieur du bloc catch vide, c'est certainement un problème. Il n'y a aucune raison de penser qu'un appel à malloc()
s'exécutera avec succès 100% du temps.
La question n'est pas spécifique à la langue, mais plutôt spécifique à l'utilisateur. Pensez à la question du point de vue de l'utilisateur. L'utilisateur fait quelque chose, comme taper le nom du programme sur une ligne de commande et appuyer sur Entrée. Qu'attend l'utilisateur? Comment peuvent-ils savoir si quelque chose s'est mal passé? Peuvent-ils se permettre d'intercéder en cas d'erreur?
Dans de nombreux types de code, ces contrôles sont exagérés. Cependant, dans le code critique de sécurité à haute fiabilité, comme ceux pour les réacteurs nucléaires, la vérification des erreurs pathologiques et les voies de récupération planifiées font partie de la nature quotidienne du travail. Cela vaut la peine de prendre le temps de demander "Que se passe-t-il si X échoue? Comment revenir à un état sûr?" Dans un code moins fiable, comme ceux pour les jeux vidéo, vous pouvez vous en sortir avec beaucoup moins de vérification d'erreurs.
Une autre approche similaire consiste à savoir dans quelle mesure vous pouvez réellement améliorer l'état en détectant l'erreur. Je ne peux pas compter le nombre de programmes C++ qui interceptent fièrement des exceptions, juste pour les renvoyer parce qu'ils ne savaient pas vraiment quoi faire avec eux ... mais ils savaient qu'ils étaient censés faire la gestion des exceptions. Ces programmes n'ont rien gagné de l'effort supplémentaire. Ajoutez uniquement le code de vérification des erreurs qui, selon vous, peut mieux gérer la situation que de ne pas simplement vérifier le code d'erreur. Cela étant dit, C++ a des règles spécifiques pour gérer les exceptions qui se produisent pendant la gestion des exceptions afin de les attraper de manière plus significative (et par là, je veux dire appeler terminate () pour vous assurer que le bûcher funéraire que vous avez construit pour vous-même s'allume). dans sa propre gloire)
Comment pouvez-vous devenir pathologique? J'ai travaillé sur un programme qui définissait un "SafetyBool" dont les valeurs vraies et fausses étaient soigneusement choisies pour avoir une distribution uniforme de 1 et de 0, et elles ont été choisies pour que l'une des pannes matérielles (flips sur un seul bit, bus de données) traces cassées, etc.) n'a pas provoqué une mauvaise interprétation d'un booléen. Inutile de dire que je ne dirais pas qu'il s'agit d'une pratique de programmation à usage général à utiliser dans n'importe quel ancien programme.
Un peu de résumé sur la question. Et ce n'est pas nécessairement pour le langage C.
Pour les programmes plus importants, vous auriez une couche d'abstraction; peut-être une partie du moteur, une bibliothèque ou dans un cadre. Cette couche ne se soucierait pas de la météo, vous obtenez des données valides ou la sortie serait une valeur par défaut: 0
, -1
, null
etc.
Ensuite, il y a une couche qui serait votre interface avec la couche abstraite, qui ferait beaucoup de gestion des erreurs et peut-être d'autres choses comme les injections de dépendance, l'écoute des événements, etc.
Et plus tard, vous auriez votre couche d'implémentation concrète où vous définissez réellement les règles et gérez la sortie.
Donc, mon opinion est qu'il est parfois préférable d'exclure complètement la gestion des erreurs d'une partie du code car cette partie ne fait tout simplement pas ce travail. Et puis avoir un processeur qui évaluerait la sortie et indiquerait une erreur.
Cela se fait principalement pour séparer les responsabilités, ce qui conduit à la lisibilité du code et à une meilleure évolutivité.
En général, sauf si vous avez une bonne raison pour pas vérifier cette condition d'erreur, vous devriez. La seule bonne raison à laquelle je peux penser pour ne pas vérifier une condition d'erreur est lorsque vous ne pouvez pas faire quelque chose de significatif en cas d'échec. C'est une barre très difficile à respecter, car il y a toujours une option viable: sortir avec élégance.