Bonjour,
La gestion des erreurs est une composante aussi importante que délicate dans une appli. Et je sollicite votre opinion sur 3 macros que je viens d'adopter dans un de mes projets.
Elles ont pour but de simplifier la gestion d'erreurs internes au moyen d'exception. Leur design vise à satisfaire les contraintes suivantes:
- simplifier au maximum le test et de remontée d'erreur (éliminer les if)
- faciliter l'écriture de messages d'erreurs détaillés (inclure les code d'erreur...)
- permettre la recherche automatique des erreurs dans le source (au moyen d'un script python...) afin de les documenter
- permettre la traduction des messages d'erreur si on le souhaite
- permettre d'inclure l'emplacement dans le source (fichier, ligne...) où l'erreur a été émise
Ces points réunis m'ont convaincu de la pertinence d'utiliser des macros que j'ai appelé throwError, throwIf et throwIfNot. Elles s'utilisent ainsi:
Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 void saveToFile(const gp::Message & msg, const fs::path & outputName) { std::ofstream file(outputName, std::ios::binary); throwIfNot(file, "Failed to create file {0}", outputName); std::string outputText; if (outputName.extension() == ".bin") { throwIfNot(msg.SerializeToString(&outputText), "Failed to encode to raw data" ); } else if (outputName.extension() == ".txt") { throwIfNot(gp::TextFormat::PrintToString(msg, &outputText), "Failed to encode to text" ); } else { throwError("Unsupported output file type {0}", outputName.extension()); } throwIfNot(file << outputText, "Failed to write to file {0}", outputName ); }
Voilà pour "l'API" de ces macros. Voici mon implémentation actuelle, basée sur la fmtlib:
Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 #include <fmt/format.h> #include <fmt/ostream.h> #define throwError(...)\ throw std::runtime_error{ fmt::format(__VA_ARGS__) } #define throwIf(cond, ...)\ for (;(cond);) {\ throwError(__VA_ARGS__);\ } #define throwIfNot(cond, ...)\ for (;!(cond);) {\ throwError(__VA_ARGS__);\ }
On pourrait de façon assez similaire se baser sur Qt, et plus précisément QObject::tr() afin de rendre les messages d'erreur traduisibles. Il n'y aurait alors que la syntaxe des paramètres acceptés qui change ("{1}" => "%1").
En ce qui me concerne, je ne suis pas fan de la création de moulte types d'exceptions. Je préfère ne lancer que des std::runtime_error et ne catcher que des std::exception. C'est à ce jour le seul souci que je vois avec ces macros : la difficulté de modifier au cas par cas le type de l'erreur levée. Mais pour moi c'est plutôt une bonne chose car j'utilise les exceptions pour remonter des erreurs critiques qui ne nécessitent (ne peuvent) pas être corrigées, mais simplement signalées. Je trouve en effet la gestion d'erreurs via une hiérarchie d'exceptions plus complexe, mais c'est un autre débat.
Qu'en pensez-vous ?
PS: l'utilisation de for(;;) dans la macro est une habitude prise vis à vis d'un warning VC++ sur le test de conditions qui est toujours vraie (si on passe "true" par exemple) mais c'est peut-être pas pertinent ici
Partager