Cette section présente la gestion d'erreur utilisée dans la bibliothèque. Il est question ici uniquement des erreurs à l'exécution et non des erreurs de compilation et d'édition de liens. Différents types d'erreurs peuvent survenir lors de l'utilisation de la bibliothèque, parmi ces erreurs, on peut distinguer les opérations d'entrées sorties (tentative de chargement d'un fichier inexistant, analyse d'un fichier graphe contenant des types qui ne sont pas définis dans le support courant, etc.), des erreurs provoquées par les opérations de graphes conceptuels (lorsque les arguments passés à l'opération ne respectent pas certaines contraintes, etc.) ou d'autres erreurs telles que le non respect des préconditions de certaines méthodes (ajout d'un élément à un ensemble qui contient déjà cet élément, etc.).
La gestion des erreurs se fait principalement par l'utilisation d'exceptions, et le lecteur non familier avec cette notion est invité à consulter une documentation de C++ pour des explications sur la façon de manipuler de tels objets.
La première partie ci-dessous décrit la façon de configurer la bibliothèque afin d'activer le contrôle d'erreur (afin de faciliter le débogage) ou de le désactiver (afin d'optimiser les performances). Une telle configuration n'est toutefois pas indispensable, vous pouvez passer directement à la section La classe Exception si vous désirez utiliser la configuration par défaut.
Au cours du développement d'une application, un grand nombre d'erreurs proviennent de paramètres invalides passés aux méthodes. La vérification de la validité de ces paramètres est inutile une fois que l'application est totalement au point. Afin de ne pas ralentir l'exécution d'une telle application, les vérifications effectuées dans la bibliothèque sont "débrayables", et il est même possible de choisir le comportement de la bibliothèque dans le cas où une erreur est détectée.
La valeur du symbole de préprocesseur ASSERT_MODE
(voir common.h) définit le traitement effectué quand une erreur est rencontrée. Si ce symbole est défini à 0
, aucune vérification n'est effectuée (à part la vérification de la syntaxe lors du chargement d'un fichier), si ce symbole vaut 1
les préconditions sont vérifiées par un appel à assert()
(voir une documentation C/C++ pour plus d'explications sur cette fonction standard) ce qui entraîne une sortie immédiate de l'application dans le cas où une précondition n'est pas vérifiée. Enfin, si ce symbole est défini à 2
, une erreur détectée provoque la levée d'une exception. Ce dernier mode est celui par défaut.
Il est possible, dans le cas où ASSERT_MODE
est défini à 1
ou 2
de choisir quelles conditions doivent être vérifiées et qu'elles peuvent être considérées comme toujours valides. De cette façon, il est possible d'accélerer l'exécution d'une application en cours de développement en choisissant d'activer la gestion d'erreur de Cogitant pour les seules opérations qui sont utilisées par la partie "en développement" de l'application. Ainsi, la partie "au point" de l'application pourra s'exécuter à une vitesse optimale. Le choix des conditions à vérifier se fait par la définition des symboles de préprocesseur CGA_xxx
(voir common.h).
Si aucune gestion d'erreur n'est activée (ASSERT_MODE=0
) des erreurs d'exécution inattendues (Segmentation fault
, Bus error
et autres joyeusetés...) peuvent se produire à l'exécution dans le cas où de mauvais paramètres sont passés aux méthodes de la plate-forme (à noter que de telles erreurs d'exécutions peuvent se produire même si une gestion d'erreur est activée car tous les cas de valeurs inappropriées ne sont pas testés). Si la gestion d'erreur par assert
est activée (ASSERT_MODE=1
), la détection d'une erreur provoque la sortie immédiate de l'application et l'affichage du message Abort
. Ceci peut être utilisé pour trouver rapidement les instructions fautives à l'aide d'un debogueur. Dans la suite de cette section, seule la gestion d'erreurs par exceptions (c'est à dire ASSERT_MODE=2
) est traitée.
Changer de mode de gestion d'erreur se fait de façons différentes selon le compilateur utilisé. Sous Unix, il faut utiliser le paramètre –enable-assert=
suivi de 0
(équivalent à no
), 1
ou 2
(équivalent à yes
), par exemple :
Sous Windows, il est nécessaire de modifier la valeur de certaines variables avec CMake comme AssertMode
et indiquer la valeur choisie. Dans tous les cas ceci ne constitue que la première étape, et il est nécessaire de recompiler complètement la bibliothèque ainsi que l'application construite sur la bibliothèque.
Afin de choisir les conditions à vérifier (symboles CGA_xxx
), il est nécessaire de modifier le fichier include/cogitant/common.h et, là aussi, de recompiler complètement la bibliothèque. Il est rappelé que la définition de tels symboles n'a aucun sens si ASSERT_MODE
est égal à 0
.
La classe cogitant::Exception est super-classe de toutes les classes représentant des exceptions. Si cette classe n'est jamais utilisée pour créer des exceptions (seules ses sous-classes sont utilisées), elle fournit un certain nombre d'attributs et méthodes communs à toutes les classes d'exceptions. Ainsi, cette classe est notamment munie d'une méthode cogitant::Exception::toString() retournant une chaîne de caractère fournissant un intitulé de l'erreur, et d'un opérateur de sortie sur un flux pour afficher l'exception. De plus, toutes les exceptions levées par les méthodes de la bibliothèque étant des instances de (sous-classes de) cogitant::Exception, il est possible de gérer toutes les exceptions en récupérant uniquement les cogitant::Exception, comme dans l'exemple ci-dessous.
Exemple. Création d'un graphe conceptuel vide et ajout d'un sommet concept à ce graphe. Ce sommet est ensuite considéré comme un sommet relation par un appel à la méthode cogitant::GraphObject::asRelation(), ce qui provoque la levée d'une exception de type cogitant::ExceptionIncorrectType, gérée dans le bloc catch
puisque cette classe est une sous classe de cogitant::Exception. Le traitement de l'exception consiste en l'affichage de cette exception (ce qui, pour une instance de cogitant::ExceptionIncorrectType, provoque l'affichage du nom de cette classe). Après le traitement (exécution du bloc catch
), l'application continue à s'exécuter, et se termine donc "sans erreur" dans le cas de cet exemple.
Parmi les sous-classes de cogitant::Exception, certaines sont utilisées dans différentes circonstances (et plus particulièrement les exceptions d'entrées sorties). Pour connaître avec précision l'erreur qui a été détecté, il est possible d'utiliser la méthode cogitant::Exception::code() qui retourne un code d'erreur, dont la sémantique dépend de la classe de l'exception (voir paragraphe suivant dans le cas des erreurs d'entrées sorties, ainsi que les pages documentant les sous-classes de cogitant::Exception).
Un grand nombre d'erreurs à l'exécution sont causées par les entrées sorties. Ces erreurs sont représentées par la classe cogitant::ExceptionIO et ses sous-classes. De telles erreurs peuvent provenir d'une tentative de lecture ou d'écriture d'un fichier de nom incorrect ou du non respect du format dans un fichier en cours d'analyse. La classe cogitant::ExceptionIO repère plus particulièrement les erreurs d'entrées sorties au niveau du fichier : problème à l'ouverture, problème de lecture ou d'écriture, extension du nom de fichier non reconnue. Ces différentes erreurs peuvent être différenciées par la valeur retournée par cogitant::Exception::code(). Par exemple, cogitant::ExceptionIO::OPEN repère un problème de lecture du fichier (sans doute inexistant) et cogitant::ExceptionIO::CREATE repère un problème de création d'un fichier (le répertoire du fichier est sans doute inaccessible).
Exemple. Pour illustrer l'utilisation de cette classe, l'exemple ci-dessous tente de charger plusieurs fichiers et affiche un message particulier quand un de ces fichiers est introuvable. Les autres exceptions sont affichées telles quelles. Ainsi, le premier et le troisième fichier sont chargés alors que pour le second, une cogitant::ExceptionIO est levée, avec le code d'erreur cogitant::ExceptionIO::OPEN, ce qui provoque l'affichage du message >>> Fichier inexistant <<<
.
Si la classe cogitant::ExceptionIO repère des erreurs d'accès aux fichiers, ses sous-classes représentent plus particulièrement le non respect du format de fichier. Ainsi, une cogitant::ExceptionInputLexeme est levée quand, dans le fichier en cours de lecture, se trouve un lexème qui n'était pas attendu. Une telle erreur est par exemple provoquée quand un fichier BCGCT ne contient pas un caractère :
après le mot-clef Concepts
(voir Grammaire du format BCGCT de CoGITaNT 5). Dans ces conditions, l'appel à cogitant::Exception::code() permet de connaître le code du lexème attendu (pour une description des codes de lexèmes de BCGCT, se reporter à la documentation de la classe cogitant::OperationBCGCTInput::IStreamBCGCT).
Parfois, un fichier est syntaxiquement correct dans le sens où il a été écrit en respectant la syntaxe du format mais il ne contient aucun objet valide pouvant être manipulé dans la bibliothèque. C'est le cas, par exemple, quand un type de concept non défini dans le support courant est utilisé dans un fichier contenant un graphe conceptuel. De telles erreurs sont gérées par les sous-classes de cogitant::ExceptionInput qui sont des classes d'exceptions spécifiques à chaque format (cogitant::ExceptionInputBCGCT, cogitant::ExceptionInputCoGXML). Ainsi, la classe cogitant::ExceptionInputBCGCT repère les erreurs lors de l'analyse d'un fichier BCGCT, et définit plusieurs codes d'erreurs qui peuvent être utilisés conjointement à la méthode cogitant::Exception::code() pour connaître la cause de l'erreur (en fait, certains de ces codes d'erreurs sont déclarés dans cogitant::ExceptionInput, et hérités dans toutes les sous-classes, donc dans cogitant::ExceptionInputBCGCT). Dans tous les cas, si ces codes d'erreur sont utiles pour tester le type d'erreur par programme, il est plus simple d'utiliser la méthode cogitant::ExceptionIO::toString() (ou l'opérateur de sortie) pour afficher l'intitulé de l'erreur à l'écran. À noter que la classe cogitant::ExceptionIO et ses sous-classes comportent une méthode cogitant::ExceptionIO::line() qui retourne le numéro de la ligne du fichier à laquelle l'erreur a été détectée.