Un sujet qui revient régulièrement quand on manipule des données issues d’une source extérieure au programme (comme un fichier local, accessible sur un partage réseau, ou encore des données stockées sur une base de données…) est la gestion des erreurs.
La gestion des erreurs est essentielle pour réagir en fonction du motif d’échec, sachant que chaque motif peut entraîner des réactions différentes : impossibilité de localiser la ressource, d’y accéder en lecture, de la parcourir intégralement, données non conformes au format attendu, etc. Les possibilités sont – malheureusement – nombreuses.
Dans la plupart des cas, il est d’ailleurs impossible d’être absolument et totalement robuste avec des manipulations telles que celles sur les fichiers.
C’est un vaste sujet mais je vais ici m’attarder sur ce qui se passe après un échec. Admettons qu’une ressource n’ait pas été lue, nous laissant avec une donnée dite « nulle » (que ce soit un Nil, un None, un NULL, un nullptr, une structure ou un objet avec des membres aux valeurs par défaut, etc… peu importe).
Comment gérer cela dans un programme ?
Je vais commencer par illustrer la mauvaise façon de procéder (cela peut paraître un peu direct, mais c’est un sujet où il n’y a pas vraiment de débat, juste des bonnes et des mauvaises pratiques). Nous avons un algorithme de calcul scientifique qui doit, au démarrage, récupérer une structure de données contenant des informations sur un climat météorologique. Pour cause d’erreur, cette structure est nulle. L’exécution du programme est néanmoins poursuivie, l’algorithme reçoit cette structure, elle est passée de fonction en fonction, toujours nulle, et chaque fonction vérifie l’état de ladite structure.
Donc, chaque fonction implémente une fonction si, n’effectuant que les calculs ne nécessitant pas cette structure lorsqu’elle est nulle… c’est-à-dire parfois aucun.
Le programme fait même appel aux GPUs disponibles dans la machine afin de paralléliser au maximum ces calculs scientifiques. Le processeur est actif, les puces graphiques sont actives, et à la fin, les résultats sont obtenus : une belle grille de 0.
Du temps de perdu, de l’électricité consommée pour rien, et un programme parsemé de fonctions si qui traitent toutes le même cas.
Nous sommes ici dans le cas typique d’une structure de données absolument primordiale pour la bonne exécution du programme. Les fonctions qui en ont besoin devraient donc toujours recevoir des données exploitables et non une structure nulle.
Alors comment faire ?
La réponse vient généralement assez naturellement. Ne vérifier qu’une fois l’état de cette structure, et surtout, le plus tôt possible. Dès que la tentative de lecture a été faite, en cas d’erreur, le programme est interrompu. Cela peut se faire par le biais d’une assertion, une exception, ou d’une boite de dialogue en avertissant l’utilisateur si l’application dispose d’une interface graphique.
Si les données ont été lues avec succès, le programme peut continuer, et les portions de code nécessitant cette structure n’auront pas à vérifier sa présence.
Bien évidemment, cela ne s’applique pas dans le cadre d’une application jugée critique et qui pourrait être exposée, par exemple, à des bitflips à cause de conditions extrêmes (température, pression, accélération…), typiquement dans le monde de l’embarqué dans les transports, où il est alors indispensable d’implémenter des mécanismes de contrôle de l’intégrité des données et des robustesses aux valeurs hors des intervalles prévus.
Ne cherchons pas la petite bête 🙂