Bon, de tous ces échanges (surtout avec Paul ) j'ai réalisé que j'avais un soucis de clarté au niveau de mon utilisation des exceptions et je comprends mieux la remarque sur leur typage. Alors je vais reformuler du mieux que je peux.
Tout d'abord, je pense que préciser le contexte est important (j'aurais du commencer par là je crois!). Quand on fait une application IHM, il peut être utile de créer différents types d'exceptions (mais je reste très réservé dans le cas de bibliothèques - je détaillerai peut-être ensuite). Dans mon cas, il s'agit de programmes (micro-services) en ligne de commande destinés à tourner sur de l'embarqué ou en tant que serveur (gRPC).
Du coup je distingue 3 familles d'erreurs:
- erreurs courantes qui sont gérées par l'appelant (find() ne touve rien...) => code de retour classique / std::optional
- les erreurs fatales qui entrainent la fermeture quasi immédiate du programme: pan t'es mort
- les erreurs inattendues où on ne peut rien faire mais qui ne remettent pas en cause l'intégrité du programme => utilisation de mes "macros"
C'est donc pour gérer des erreurs peu courantes mais non fatales que j'utilise ces "macros". Chaque erreur:
- est susceptible d'être reportée et ignorée. Ex: le serveur échoue à traiter une requête, il catch et forward l'erreur depuis son handler ("traitement générique"), et il continue sa vie tranquillou pépère... à condition de tout bien nettoyer (RAII) => d'où mon besoin de tester automatiquement toutes les erreurs de ce type.
- l'erreur est suceptible d'être transmise (sérialisée) par le réseau et donc d'être affichée auprès de l'utilisateur. Je gagne en simplicité à générer in-situ le message texte descriptif. On peut aussi me demander plus tard de les traduire.
J'espère que c'est plus clair
Par commodité j'ai choisi d'utiliser std::runtime_error pour gérer ces erreurs. Peut-être que c'est pas une bonne idée et que je devrais créer un type spécifique? A voir.
Ce qui est sûr c'est que pour ASSERT_ALWAYS j'ai fait l'erreur de la mapper sur ces "macros" alors qu'elle est utilisée dans un contexte d'erreur fatale (par rapport à assert, ASSERT_ALWAYS reste en debug). Du coup je vais la conserver comme à l'origine : appel de std::abort : pan t'es mort!
Au passage, rapellons que si y'a pas de catch{} qui englobe un throw, alors le dépilage de la pile est facultatif du point de vue de la norme et donc zappé par certains compilos (bye bye le RAII!). Faut donc bien savoir ce qu'on fait quand on ne met pas de catch dans son main()!
Partager