Accueil > professional > Pourquoi les exceptions vérifiées sont une mauvaise idée

Pourquoi les exceptions vérifiées sont une mauvaise idée

Dans la série « je blogue pour éviter de me répéter », je demande les exceptions vérifiées.

Attendez, je vous devine: c’est quoi une exception vérifiée ?

C’est une invention de Java (à ma connaissance) consistant en une exception que le contexte est obligé de considérer

Autrement dit, si j’écris:

throw new MonExceptionVerifiee();

avec

public class MonExceptionVerifiee extends Exception

alors le code entourant cette instanciation est obligé de prendre en compte l’exception.

Deux façons pour cela, la première consistant à relancer l’exception pour ne pas avoir à s’en préocupper :

public void maMethode() throws MonExceptionVerifiee {	
	throw new MonExceptionVerifiee("Pour la cause");
}

Le throws au niveau du nom de la méthode est ce qui permet de relancer l’exception.

La seconde façon consiste à attraper l’exception pour la traiter, comme suit:

public void monAutreMethode() {	
	try {
		throw new MonExceptionVerifiee("Pour la cause");
	} catch(MonExceptionVerifiee e){
		// ?
	}
}

Bon, là ça présente peu d’intérêt, en général on attrape plutôt les exceptions vérifiées lancées par les autres, par exemple :

public void monAutreMethode() {	
	try {
		maMethode();
	} catch(MonExceptionVerifiee e){
		// ?
	}
}

Pour finir avec l’introduction aux exceptions vérifiées, sachez qu’une exception, pour être vérifiée, doit hériter de Throwable ou Exception, mais qu’elle ne l’est pas si elle hérite d’Error ou de RuntimeException, ce qui la hiérarchie des exceptions Java suivante :

Throwable (vérifiée) <- Error (non vérifiée)
^
|

Exception (vérifiée)
^
|
RuntimeException (non vérifiée)

Autrement dit, Throwable est tout en haut et est une exception vérifiée. Error étend Throwable, et n'est pas vérifiée. Exception étend Throwable, et est vérifiée. RuntimeException étend Exception et… n'est pas vérifiée.

Alambiqué hein ? Je suis d'accord, et en général si la conception est compliquée, alors l'idée est foireuse. Mais j'anticipe.

En effet, pourquoi diable les exceptions vérifiées?

L'intention initiale est de forcer le code à considérer les exceptions. En effet, comme cela, en lisant un fichier, je serai bien conscient que la lecture de ce dernier peut planter. Certes, c'est appréciable, mais que puis je faire pour autant ? Et là est tout le mal des exceptions vérifiées. Leur traitement n'est pas simple et, surtout, rien ne garanti qu'il sera bien fait.

Revenons à l'exemple précédent :

public void monAutreMethode() {	
	try {
		maMethode();
	} catch(MonExceptionVerifiee e){
		// ?
	}
}

A noter que si une méthode lance plusieurs exceptions vérifiées, on est obligé de les traiter chacune. Par exemple, l’autre de maMethode pourrait choisir de lancer également des IOException, comme suit :

public void maMethode() throws MonExceptionVerifiee, IOException {	
	throw new MonExceptionVerifiee("Pour la cause");
}

alors la méthode appelante sera du genre :

public void monAutreMethode() {	
	try {
		maMethode();
	} catch(MonExceptionVerifiee e){
		// ?
	} catch(IOException e){
		// ??
	}
}

Verbeux. Et embêtant: la prochaine fois que l’auteur de maMethode choisira d’ajouter un throw, il faudra que je change à nouveau mon code. Que faire quand on est malin ? Facile, l’obligation de traiter les exceptions vérifiées prend en compte la hiérarchie, du coup je peux écrire ça :

public void monAutreMethode() {	
	try {
		maMethode();
	} catch(Throwable t){
		// ?
	}
}

Tadamm ! C’est nettement moins verbeux, et même plus robuste au changement : le petit gars derrière maMethode peut se lâcher. Désormais, j’suis blindé.

Vous noterez le commentaire « // ? » dans le catch, vu que sur le coup je ne savais pas quoi faire. Faire un tel catch qui ne renvoie aucune exception s’appelle avaler une exception, ou dans le cas présent avaler toutes les exceptions. Fort hein ? Ca semble la parade ultime…

Sauf que… sauf que désormais, plus aucune exception ne remonte. Vérifiée ou pas. Lancée par le programme ou la JVM (toutes les exceptions découlant d’Error). Nada. Rien. Et là, bonjour l’enfer : désormais en cas de plantage, plus aucune info n’est dispo. Avec un peu de chance l’appelant se rend compte qu’un effet de bord n’est pas présent (genre je clique et rien ne se passe), mais ce n’est pas garanti. A débugger, cela relève des travaux d’Hercule : aucune info, sauf que ça marche pas. Pas de trace.

Les seules options que je voie est de modifier tous les catch (ah ah ! – surtout sur du code tiers) ou de s’taper toute l’application au débuggeur… Dans les deux cas, vous serez forcé de faire ça après coup sans aucun élément sur l’origine du problème. Dans les deux cas, vous chercherez une aiguille dans une botte de foin. Et si votre code contient beaucoup de catch avalant des exceptions, vous avez un sacré paquet d’aiguilles différentes à chercher une à une, tout au long de la vie de votre application…

Bien sûr, me direz vous, aucun développeur digne de ce nom n’aurait la bonne idée d’avaler toutes les exceptions. Mais le catch vide est juste très facile, tentant même. Dans l’exemple ci dessus, dans monAutreMethode, je ne savais pas quoi mettre lors de la rédaction de l’exemple, j’ai mis un commentaire vide, le // ?, pour remplir. Et devinez quoi, ça compile et ça peut partir en prod sans problème! Combien de développeurs se retrouvent exactement dans la même situation ? Combien laissent un commentaire type « TODO » et autres « Euh, à traiter? » puis continuent comme si de rien n’était ? Beaucoup. Trop. De plus, cela semble résoudre le problème du traitement d’erreur, de ces satanés IOEXception dont on ne sait que faire…

Sans compter que cela ne semble pas être une si mauvaise pratique : eclipse propose par défaut de quasiment avaler les exceptions vérifiées. En effet, le quick fix par défaut d’eclipse pour gérer cette fameuse maMethode() throws MonExceptionVerifiee, IOException est le suivant :

try {
    maMethode();
} catch (MonExceptionVerifiee e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

Génial non ? Exception avalée, pile d’appels imprimée mais… sans le message de l’exception (accessible via e.getMessage()). Noter cet appel direct à e.printStackTrace() : votre API de logging préférée n’a aucune chance de s’en mêler.

Bref, le professionnalisme est mis à rude épreuve et avaler les exceptions semble la bonne solution. Du coup, chaque code lançant une exception vérifiée est à la merci du premier développeur commettant une erreur dans son traitement. Quelques lignes et zou plus rien, des erreurs cachées et une application à débugger dès qu’on se rendra compte de l’erreur. Peut être après mille appels à la méthode plantant. Peut être après milles confirmations envoyées à tort. Splendide et… imparable.

Pour la petite histoire, avaler les exceptions n’est pas toujours le fait de développeurs. Eh oui, j’ai vu des architectes, et pas nécessairement Java, faire ainsi dans leur code. Sur toute l’application. A chaque fois qu’ils pouvaient se faire mordre par une saleté d’exception. Pour la défense de ces érudits, notons que nos glorieuses écoles d’ingénieur en informatique n’enseignent rien en la matière. J’ai discuté avec de nombreux étudiants de nombreuses (hautes) écoles différentes, ou d’université, et jamais le thème n’avait été abordé. Après tout, les exceptions, c’est pour ceux qui font du mauvais code en premier lieu non ?

In fine, sommes nous désormais d’accord que les exceptions vérifiées sont une mauvaise chose ? Rassurez vous, y a moyen de les traiter correctement : c’est l’objet de l’article suivant, Patates et exceptions. On se voit là bas 😉

Publicités
Étiquettes : , , ,
  1. mai 31, 2013 à 9:01

    Pour la petite histoire, je me suis déjà pris des baffes parce que je levais des RuntimeException que des « blaireaux qui ne lisent pas les docs parce que c’est inutiles » ne pouvaient pas traiter par un catch vide.

    Par contre Chef, il existe le multicatch depuis Java7, donc le problème de lisibilité est « réglé » 😉

  2. mai 31, 2013 à 9:36

    J’adore la petite histoire… L’antithèse ce ce qu’il faut faire… Comment as tu fait ensuite? Un throw Throwable sans doute ? lol

    Pour le mutlicatch, j’en parle dans l’article qui suit, Patates et exceptions. Là je ne voulais qu’expliquer le pourquoi du « c’est pas bien ». Et avaler des exceptions via plusieurs catch ou un multicatch, ça ne change pas la donne, c’est toujours pas bien… Triste monde 😉

  3. thierry
    août 19, 2015 à 9:44

    Quand j’utilise du code tiers, je préfère que les exceptions soient vérifiées et fassent partie intégrante de la signature. Quitte à les retyper ensuite en exception de mon modèle, vérifiée ou non tant que ça reste dans mon domaine de code.
    Merci pour l’article, je suis tombé dessus en cherchant le mot clé « avaler », je ne savais si ça se disait comme ça en français.

  4. août 19, 2015 à 10:04

    Merci pour le commentaire et l’invitation au débat, allons y 😉

    Première question : quelle probabilité que le code tiers n’émette que les exceptions vérifiées déclarées ? Quasi nulle.

    Aussi, même s’il y a des exceptions vérifiées dans les signatures, il faut envisager la probabilité d’exception non vérifiée.

    A l’inverse, quelle probabilité que l’utilisateur de la librairie se contente d’avaler les exceptions vérifiées (et ne fasse rien pour celles non vérifiées) ? Non négligeable.

    Aussi, mettre de suite des exceptions non vérifiées permet d’éviter de dépendre de la qualité du développeur utilisant l’API. Et s’il est bon et considère déjà les exceptions non vérifiées, il a toujours bon sans boulot supplémentaire. Que du bonheur non ?

    Ceci dit, perso j’aime les méthodes qui déclarent les exceptions lancées « communément », et j’imagine que cela motive ton souhait d’exception vérifiée.

    Mais en fait la déclaration des exceptions lancées fonctionne tout autant avec les exceptions non vérifiées, par exemple :

    public Foo foo() throws MonExceptionNonVerifiee

    Et je plussoie pleinement ce genre de signature.

    D’ailleurs, quand je fais une API, je fais si nécessaire une classe d’exception non vérifiée chapeau, afin de faciliter un éventuel travail de traitement fin des exceptions du client.

    Qu’en dis tu ?

    ++

  1. mai 30, 2013 à 9:46

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :