Le logiciel BaseDeFiches a été développé sur plusieurs années et même si le développeur est unique, il en résulte des pratiques de programmation fluctuantes en fonction des découvertes, des nouvelles théories ou de nouvelles lubies.
Le texte présent est plus un pense-bête pour essayer de fixer une pratique commune. Une des parties les plus pénibles dans la programmation est le traitement des erreurs. Sauf à programmer une interface complexe et lourde qui prend l’utilisateur par la main de bout en bout, la solution la plus pratique pour configurer les applications les plus pointues (par exemple, le balayage et l’exportation des données) est celle de fichiers de configuration où l’administrateur doit respecter une syntaxe particulière. C’est la solution la plus efficace pour offrir une large palette de possibilité : l’administrateur n’a qu’à saisir une lettre particulière pour changer totalement la fonction. C’est ce qui explique d’ailleurs la prospérité de la ligne de commande (l’expression « simple et puissante » fait toujours sourire mais cela décrit souvent une application en ligne effectivement simple quand on sait s’en servir).
Qui dit syntaxe dit erreur de syntaxe. Il faut que le logiciel puisse remonter de la manière la plus précise l’erreur de syntaxe à l’utilisateur pour qu’il puisse faire la correction rapidement. Il faut enfin faire la part entre les erreurs de l’utilisateur et les erreurs de programmation inévitables.
Exceptions existants dans Java :
IllegalStateException
: cette exception a été utilisée assez abondamment et de manière abusive pour toutes les erreurs de programmation, son usage doit être repris pour revenir à son but initial, signaler les erreurs d’état (méthode appelée trop tôt par exemple), on les fera commencer par « not set yet » quand une valeur n’a pas encore été initialisée.
Ce type d’exception ne devra notamment ne jamais être « capturé » avant d’être signalé à l’utilisateur final, celui-ci pouvant ainsi faire un rapport de bogue.
Enfin, ces erreur découlant de problèmes manifestes de programmation , elles ne seront pas signalées dans la documentation, la consultation du code source suffisant au programmeur à comprendre le problème.
ClassCastException
: elle ne sera jamais utilisée pour signaler un problème car elle doit toujours être considérée comme résultant d’une erreur manifeste de programmation. Elle ne devra pas être capturée non plus. On se basera toujours sur un test instanceof
plutôt que de se baser sur sa capture. Dans le cas d’implémentation de l’interface List
ou équivalent, on fournira la documentation ou les tests nécessaires afin de la prévenir. Enfin, on la remplacera par une exception IllegalArgumentException lors d’un problème lié à la classe d’un argument.
l’exception ClassCastException ne sera jamais documentée.
IndexOutOfBoundsException
: elle ne sera utilisée que dans les implémentations de l’interface List
ou dans des manipulations équivalentes à des manipulations sur une liste.
Elle ne sera jamais capturée (sauf transformation en IllegalArgumentException) ou documentée.
NullPointerException
: elle ne sera jamais utilisée ou documentée et ainsi réservée à une erreur manifeste de programmation. Dans le cas d’un argument nul, on préferera IllegalArgumentException
UnsupportedOperationException
: elle pourra être utilisée de façon parcimonieuse dans certaines interfaces et leurs implémentations, notamment lorsqu’un même objet peut avoir des états distincts (voir la classe Personne). Une documentation suffisante ou des méthodes ad hoc devront être fournies pour les prévenir.
IllegalArgumentException
: elle est très souvent utilisée (mais pas toujours systématiquement), en particulier pour toutes les classes de stockage d’information où une valeur nulle n’est pas problématique dans l’instant mais peut l’être au cours d’un traitement réalisé bien après.
Exceptions « maisons »
Aux exceptions de Java, ont été rajoutées des exceptions qui étendent RuntimeException et qui ne doivent pas être capturées car elles dénoncent un bogue dans le logiciel, elles ne sont pas non plus documentées ou signalées :
ImplementationException
: indique que le comportement d’un objet n’est pas conforme aux spécifications de l’interface, elle est utilisée en particulier quand une exception inattendue est lancée par l’objet.
InternalResourceException
: indique qu’une ressource interne au logiciel a un problème (c’est une ressource autre qu’une classe Java mais qui est tout aussi partie intégrante du logiciel).
SwitchException
: indique que dans un switch
(ou un else
), on atteint une valeur (ou la valeur par défaut), qui ne devrait pas être atteinte. Son message indique la valeur incorrecte ou les raisons qui fait qu’elle ne peut pas être atteinte. Cette exception n’est pas utilisée dans le cas d’un switch dans une méthode publique où l’erreur vient directement de l’argument
ShouldNotOccurException
: indique un endroit où, normalement, le programme ne devrait jamais arriver (évite notamment les captures d’exception vide).
À ces quatre exceptions, se rajoute les quatre suivantes qui sont des extensions de RuntimeException et qui servent à transmettre des exceptions sans avoir à les signaler (et permettre la capture en tout fin de chaîne) :
NestedIOException
: encapsule une instance de IOException
NestedTransformerException
: encapsule une instance de TransformerException
NestedSAXException
: encapsule une instance de SAXException
NestedLibraryException
: encapsule des exceptions propres à des bibliothèques particulières
Enfin il faut signaler BdfStorageException
qui est envoyée s’il y a un problème dans le stockage des données.
Analyse du texte
Pour signaler les erreurs au moment de l’analyse d’une chaîne de caractères, on privilégiera trois solutions :
Envoyer une instance de IllegalArgumentException
Envoyer une instance de ParseException
Définir une interface spécifique appelée ErrorHandler
L’envoi d’une instance de IllegalArgumentException est à privilégier lorsque les valeurs sont fixes (par exemple, l’expression sous forme de caractères d’une constante numériques ou les différentes valeurs autorisées d’un paramètre).
L’envoi d’une instance de ParseException est à privilégier pour toutes les chaînes courtes où les causes d’invalidation de la chaîne sont réduites (grosso modo soit la chaîne est correcte, soit elle ne l’est pas, sans possibilité de rattraper le cout). Le problème de l’envoi de ParseException est son peu de détail sur les causes.
L’utilisation d’une interface spécifique ErrorHandler est à utiliser dans tous les analyses complexes où certaines parties du texte sont correctes et d’autres non, où les causes sont multiples. Les méthodes se présenteront sous la forme suivante :
Object parse(String s, ErrorHandler errorHandler)
La méthode n’envoie pas d’exception. Il y a trois cas de figure :
parse() renvoie un objet sans signaler d’erreur : tout s’est bien passé
parse() renvoie un objet en signalant des erreurs : des erreurs ont été rencontrées mais l’objet a pu être construit, c’est à l’appelant de décider s’il continue ou non le traitement au vu des erreurs signalées
parse() retourne null : les erreurs rencontrées et signalées dans ErrorHandler sont trop graves pour continuer.
Cette procédure impose d’implémenter à chaque fois les ErrorHandler. Ceci seront conçu le plus fin possible afin de signaler les erreurs exactes, à l’appelant de décider ce qu’il en fait (il peut utiliser un système de log, envoyer une exception, etc.).
À éviter (et corriger quand c’est fait) : l’utilisation des Logs qui ne sont pas assez fin pour rapporter les problèmes