Opinion sans originalité sur les exceptions
Cet article est en réalité un courriel répondant à une question sur les bonnes pratiques avec les exceptions. S’il peut être utile à quelqu’un, tant mieux!
Premièrement, l’Exception Management Application Block est surtout fait pour “réagir” et publier les exceptions à l’extérieur du contexte d’exécution du code. Il n’aide donc, selon moi, aucunement à la bonne gestion des erreur à l’intérieur du code.
Généralement on arrive devant un des cas suivants:
- L’exception est lancée dans un contexte transactionnel, dans quel cas le catch et le finally doivent s’assurer de faire le rollback, et laisser la classe appellante faire son rollback aussi. Dans ces cas là on ne veut pas gérer l’exception elle même, seulement s’assurer du bon fonctionnement du code.
- L’exception est lancée à un niveau du stack directement causée par une action de l’utilisateur (par exemple, un paramètre invalide non vérifiable par des validateurs, ou une valeur invalide dans le querystring). Dans ces cas là, on peut simplement gérer l’erreur sur place (afficher une page disant que la page a reçu un paramètre invalide) ou, ma préférence personnelle, laisser l’erreur remonter jusqu’au handler principal (généralement le Main dans une application windows, ou le Global_asax.Application_Error dans les application web).
- L’exception est récupérable (par exemple, le caching plante). Dans ces cas là (plutôt rares), simplement récupérer (et s’assurer qu’il n’est pas possible d’éviter qu’il y aie une exception en remplaçant le code fautif par une vérification des paramètres! Les exceptions ne devraient jamais faire partie intégrante d’un flow “valide” du code) et publier l’erreur.
J’ai toujours créé un assemblage “partagé” contenant les exceptions, les fonctions de logging, etc. par le passé. Je créais toujours des exceptions héritées de ApplicationException (du genre MyProjectException), de laquelle toutes mes exceptions précises héritaient. Avec le temps j’ai réalisé que ça ne me servait strictement à rien. Tant que le message d’exception est clair, et qu’il n’y a pas de besoin de catcher un type précis, une ApplicationException ordinaire fait le travail.
Personnellement je ne suis pas un fanatique de l’Application Block de gestion d’exception. C’est pratique, mais généralement un simple logging au niveau le plus haut de l’architecture fait amplement le travail. (Ça veut pas dire de pas l’utiliser, seulement que je ne donnerais pas de points pour ça dans une architecture). Je ne fais des throws (incluant l’exception initiale) que lorsque de l’information supplémentaire est disponible, sinon je la laisse passer. Exemple:
public bool VerifyCompatibility( Computer c, Version v )
{
—if( c == null )
—-throw new ApplicationException( “Computer parameter must not be null” );
—…
}
public void RegisterComputer( int computerId )
{
—try
—{
—-if( VerifyCompatibility(
——ComputerRegistry.Computer[computerId],
——Versions.CurrentVersion )
——)
——…
—}
—catch( Exception exc )
—{
—-throw new Exception( “Could not register computer ” + computerId, exc );
—}
}
Dans ce cas précédent, si on avait pas encapsulé l’exception, il aurait été très difficile de retracer le cas fautif. Tandis que maintenant on a les exceptions:
[Computer parameter must not be null]
[Could not register computer 689]
L’information, ajoutée à l’URL, l’utilisateur en cours, les informations du navigateur client et le stack trace est suffisante pour reproduire l’erreur et la régler au niveau où il convient. Bref, les exceptions doivent être complémentaires, et servir à:
– Faire faillir le code le plus vite et facilement possible pour détecter les bogues tôt dans le développement
– Avoir l’information la plus concise et complète possible pour reproduire les erreurs facilement.
En bref, les exceptions ne devraient servir à rien d’autre qu’à déboguer et logger. Si vous jugez pertinent de créer une exception spéciale qui contient plus d’information (comme le niveau de gravité de l’erreur ou un message à afficher au client), amusez vous, mais rappellez-vous que généralement on ne log et n’utilise que le message et le stack trace d’une exception.
Une chose importante: une exception ne doit en aucun cas être affichée à l’utilisateur, directement ou indirectement, ni servir à générer un message utilisateur. Si une exception représente un paramètre invalide (disons une lettre dans un TextBox qui devrait contenir un chiffre), c’est effectivement une erreur du programme, et non pas de l’utilisateur. Pourquoi? Une validation de l’entrée de l’utilisateur aurait dû avoir lieu. Par exemple, un achat avec un numéro de carte de crédit invalide qui lance une erreur est un problème majeur, car il y aurait dû y avoir un ValidateCreditCardNumber avant, ou la méthode pourrait retourner un enum du statut de la commande.
À vous de juger! Mon mentra est: léger et complet, et ne jamais laisser passer un “Object not set to an instance of an object”! Jamais!