Fatalisme dans la gestion des erreurs

Préface


Cet article est une réaction à l'article: qu'adviendra-t-il de la gestion des erreurs en C ++ 2a . Après chaque paragraphe, j'ai eu des démangeaisons, des plaies cicatrisées se sont ouvertes et ont commencé à saigner. Peut-être que je prends ce qui est écrit trop près de mon cœur. Mais je veux juste hurler sur la myopie et l'analphabétisme que les programmeurs C ++ montrent au 21e siècle. Et même pas au début.


Commençons.


Classification


Classiquement, toutes les situations erronées du programme peuvent être divisées en 2 grands groupes:
  1. Erreurs fatales.
  2. Erreurs non fatales ou attendues.


Je vais maintenant trouver à redire. Mais des erreurs fatales - elles sont aussi en un sens attendues. Nous nous attendons à ce que le voyage de mémoire mène souvent à une chute, mais peut ne pas y conduire. Et cela est prévu, n'est-ce pas? Lorsqu'une classification est introduite, il serait toujours possible de vérifier sa cohérence.


Mais c'est le cas, une erreur subtile fréquente.


Regardons les erreurs fatales.


Division par 0 . Je me demande pourquoi cette erreur est fatale? J'aimerais jeter une exception dans ce cas et l'attraper pour un traitement ultérieur. Pourquoi est-elle mortelle? Pourquoi m'impose-t-on un certain comportement de mon propre programme et je ne peux en aucun cas l'influencer? Le C ++ ne concerne-t-il pas la flexibilité et le fait que le langage soit tourné vers le programmeur? Bien que ...


Déréférencement de pointeur nul . Je rappelle immédiatement Java, il y a une NullPointerException qui peut être gérée. Il y a aussi une NullPointerException bibliothèque Poco! Alors pourquoi les développeurs standard avec la ténacité des sourds-muets répètent-ils le même mantra?


En général, pourquoi ai-je commencé ce sujet? Il est très important et révèle simplement la compréhension du développeur sur la gestion des erreurs. Il ne s'agit pas de gestion des erreurs en soi, c'est un prélude à une action importante. Il s'agit toujours de la fiabilité de l'application, de la tolérance aux pannes, et parfois, dans les types de programmes les plus rares et les plus menacés, je dirais même, de comportements transactionnels et cohérents.


Dans cet aspect, tous les différends concernant la division par zéro et les pointeurs de déréférencement ressemblent à une lutte d'oiseaux pour une miette de pain. Certainement un processus important. Mais seulement du point de vue des oiseaux.


Revenons à la division du fatalisme et à son absence ... Je vais commencer par une question simple: si j'ai reçu des données incorrectes sur le réseau, est-ce une erreur fatale?


Réponse simple et correcte: dépend de. Il est clair que dans la plupart des cas, il ne s'agit pas d'une erreur fatale, et toutes les données reçues sur le réseau doivent être validées et retournées 4xx si les données sont incorrectes. Y a-t-il des cas où vous devez planter? Et s'est écrasé avec un hurlement sauvage, pour que les SMS arrivent, par exemple. Oui, et pas un.


Il y en a. Je peux donner un exemple de mon domaine: un algorithme de consensus distribué. Un nœud reçoit une réponse qui contient un hachage d'une chaîne de modifications d'un autre nœud. Et ce hachage est différent du hachage local. Cela signifie que quelque chose s'est mal passé et poursuivre l'exécution est tout simplement dangereux: les données peuvent diverger, si ce n'est déjà fait. Cela se produit lorsque la disponibilité d'un service est moins importante que sa cohérence. Dans ce cas, nous devons tomber, et avec un rugissement, pour que tout le monde entende. C'est-à-dire nous avons reçu des données sur le réseau, ils ont validé et sont tombés. Pour nous, cette erreur n'est nulle part plus fatale. Cette erreur est-elle attendue? Eh bien, oui, nous avons écrit le code avec validation. Il est insensé de dire le contraire. Seulement, nous ne voulons pas continuer le programme après cela. Intervention manuelle requise, l'automatisation n'a pas fonctionné.


Choix du fatalisme


La chose la moins évidente dans la gestion des erreurs est de décider ce qui est fatal et ce qui ne l'est pas. Mais chaque programmeur se pose cette question tout au long de l'activité de développement. Par conséquent, il répond en quelque sorte à cette question. La bonne réponse vient de la pratique pour une raison quelconque.


Cependant, ce n'est que la partie visible de l'iceberg. Au fond, il y a une question beaucoup plus monstrueuse. Pour comprendre toute la profondeur, vous devez définir une tâche simple et essayer d'y répondre.


Défi . Faites un cadre pour quelque chose.


Tout est simple. Nous faisons un cadre, par exemple, d'interaction réseau. Ou analyser JSON. Ou, au pire, XML. La question se pose immédiatement: mais quand une erreur se produit à partir du socket - est-ce une erreur fatale ou non? Pour paraphraser: dois-je lever une exception ou renvoyer une erreur? Est-ce une situation exceptionnelle ou non? Ou peut retourner std::optional ? Ou une religieuse? (^ 1)


La conclusion paradoxale est que le cadre lui-même ne peut pas répondre à cette question. Seul le code l'utilisant le sait. C'est pourquoi l'excellente bibliothèque boost.asio , à mon avis, utilise les deux options. En fonction des préférences personnelles de l'auteur du code et de la logique appliquée, l'une ou l'autre méthode de gestion des erreurs peut être choisie. Au début, j'étais un peu gêné par cette approche, mais au fil du temps, j'ai été imprégné de la flexibilité de cette approche.


Mais ce n'est pas tout. Le pire est à venir. Ici, nous écrivons du code d'application, mais il nous semble qu'il est appliqué. Pour un autre code, de niveau supérieur, notre code sera bibliothèque. C'est-à-dire la division en code application / bibliothèque (framework, etc.) est une pure convention, qui dépend du niveau de réutilisation des composants. Vous pouvez toujours visser quelque chose sur le dessus et le code d'application cessera de l'être. Et cela signifie immédiatement que le choix de ce qui est valide et de ce qui ne l'est pas est déjà décidé par le code qui utilise et non utilisé.


Si nous sautons sur le côté, il s'avère que parfois, il n'est même pas possible de comprendre qui utilise qui. C'est-à-dire le composant A peut utiliser le composant B et le composant B le composant A (^ 2). C'est-à-dire qui détermine ce qui se passera n'est pas clair du tout.


Enchevêtrement se démêlant


Quand on regarde toute cette honte, la question se pose immédiatement: comment vivre avec? Que faire Quelles lignes directrices choisir pour ne pas se noyer dans la variété?


Pour ce faire, il est utile de regarder autour de vous et de comprendre comment ces problèmes sont résolus ailleurs. Cependant, il faut chercher sagement: il faut distinguer la "philatélie" des solutions complètes.


Qu'est-ce que la philatélie? Il s'agit d'un terme collectif, ce qui signifie que nous avons échangé un objectif mais autre chose. Par exemple: nous avions un objectif - appeler et communiquer avec des êtres chers. Et nous avons une fois, et acheté un jouet cher, car "à la mode" et "beau" (^ 3). Est-ce familier? Pensez-vous que cela n'arrive pas aux programmeurs? Ne vous flattez pas.


La gestion des erreurs n'est pas l'objectif. Chaque fois que nous parlons de gestion des erreurs, nous nous arrêtons immédiatement. Parce que c'est un moyen d'atteindre un objectif. Et l'objectif initial est de rendre notre logiciel fiable, simple et compréhensible. Ce sont ces objectifs qui doivent être fixés et toujours respectés. Et la gestion des erreurs est une connerie qui ne vaut pas la peine d'être discutée. Je veux lever une exception - mais pour la santé! Renvoyé une erreur - bravo! Vous voulez une monade? Félicitations, vous avez créé l'illusion d'avancement, mais uniquement dans votre propre tête (^ 4).


Ensuite, j'ai voulu écrire comment le faire correctement, mais j'ai déjà écrit. Les blessures ont guéri et ont cessé de saigner. En bref, les conseils sont les suivants:


  1. Séparer en composants avec des limites claires.
  2. Aux frontières, décrivez quoi et comment il peut voler. Il est souhaitable qu'il soit uniforme. Mais c'est beaucoup plus important.
  3. Facilitez la gestion des erreurs dans le code qui l'utilisera.
  4. Si quelque chose peut être traité en interne sans charger le code utilisateur, ne le collez pas. Moins un utilisateur doit gérer d'erreurs, mieux c'est.
  5. Respectez votre utilisateur, ne soyez pas des connards! Écrivez des interfaces intuitives avec le comportement attendu afin qu'il n'ait pas besoin de lire les commentaires et de jurer.

Le 5ème conseil est le plus important, car il combine les quatre premiers.


PS Dans l'enfance, j'ai toujours été curieux de regarder la fourmilière. Des milliers de fourmis, tout le monde fait quelque chose, rampe sur son entreprise. Le processus est en marche. Maintenant, je regarde aussi avec intérêt. Aussi derrière la fourmilière. Où des milliers de personnes font leur petite chose. Je peux leur souhaiter bonne chance dans leurs affaires difficiles!


^ 1: Les gens aiment les choses à la mode. Quand tout le monde a suffisamment joué, les programmeurs C ++ se sont réveillés, puis tout s'est retourné.


^ 2: Cela peut se produire lorsqu'il existe plusieurs abstractions dans le composant B qui les connectent. Voir Inversion de contrôle .


^ 3: Et le lendemain, bam, et l'écran s'est écrasé.


^ 4: Je ne suis pas contre les monades, je suis contre l'aspiration, comme, regardez, voici la monade, c'est-à-dire monoïde dans la catégorie des endofoncteurs monoïdes! Des applaudissements et des hochements de tête approbateurs se font entendre. Et quelque part loin, loin, à peine audible, quelqu'un orgasme.

Source: https://habr.com/ru/post/fr426971/


All Articles