Archive

Posts Tagged ‘clean code’

Book review: Growing Object-Oriented Software, Guided by Tests

mars 17, 2014 1 commentaire

Not being fully satisfied about my TDD practices, I was reading The Failures of « Intro to TDD », which sums up nicely bits of the uneasiness I’m facing and offers a nice way of handling it. While tweeting about the article, I was once more told to read Growing Object-Oriented Software, Guided by Tests, and so I finally did and here is my feedback on it 🙂

Lire la suite…

Étiquettes : , , , ,

Présentation Coder propre en ligne :)

février 20, 2014 1 commentaire

Presque un an après les quelques confs sur le sujet, j’ai enfin pondu la version oueb présentable, par là : Coder Propre

Un bon bout de temps me direz vous ? En effet, mais le hic était que la présentation initiale était « zen ». Autrement dit, des diapos minimalistes, avec très peu de contenu afin de préserver autant que possible le discours. A priori ça avait plutôt bien marché, mais par contre, traduire ça en présentation à lire, galère… Sans compter que le bébé était en impress.js, soit pas quelque chose qui ait prévu dès le début d’avoir des notes associées…

Bref, pour la prochaine fois, je me dis que peut être je ferai le tout direct en diapos. Certes, ça rend moins bien qu’une présentation JS avec des transitions minimalistes et sympas. Mais bon, c’est ensuite bien plus simple à mettre en ligne, surtout quand on se dit que cela pourrait être utile pour plus tard à l’auditoire.

A propos, si vous en faisiez parti, ne soyez pas surpris si le contenu diffère un peu de la présentation : le format m’a fait réaliser quelques coupes. Des bouts qui étaient à priori confus et que je tentais d’éclaircir par le verbiage l’ont été à la hache.

Bref, tout retour bienvenu, comme d’hab, et voyons si j’ai le courage de m’atteler à une présentation du Domain Driven Design, la suite logique, AMHA, de la présentation de l’an passé 🙂

++

Étiquettes : , ,

Patates et exceptions

mai 30, 2013 1 commentaire

On a vu précédement pourquoi les exceptions vérifiées sont une mauvaise idée (que je vous conseille de lire en préambule), voyons à présent comment bien gérer les exceptions.

Rassurez vous, c’est tout simple : faut voir ça comme le jeu de la patate chaude. Par défaut, on sait pas quoi en faire et faut juste refiler le bébé le plus vite possible aux autres. Facile non ?

Bon, on est joueur, on va faire ça en trois parties :
1. Passer la patate efficacement
2. Savoir mettre le feu à la patate
3. l’art de bien attraper la patate

1. Passer la patate efficacement

Comme vous le savez, bien jouer à la patate chaude implique, surtout, de ne pas avoir à s’occuper de la patate. Si on ne l’a pas, on risque pas de se rater lors de la relance. En Java, c’est pareil. Si on ne sait pas quoi faire d’une exception, le mieux est de la laisser filer. On ne l’attrape (catch) pas, rien, nada.

Bien sûr, on a ces merveilles d’exceptions vérifiées (Checked exception), celles qui vous forcent à les catcher. On en fait quoi alors ? C’est tout simple, on en fait des RuntimeException, par exemple :

try {
	// code lançant des exceptions vérifiées
} catch (MonExceptionVerifiee e){
	throw ExceptionUtils.wrap(e);
}

avec :

public class ExceptionUtils {
	
	public static RuntimeException wrap(Throwable t){
        if (e instanceof RuntimeException) {
            return (RuntimeException) e;
        } else {
            return new RuntimeException(e);
        }
	}
}

Du coup l’appelant n’à rien à attraper et l’exception sera traitée à plus haut niveau. La vie est belle non ?

Certes, par rapport aux exceptions vérifiées, l’appelant en sait moins sur les façons dont votre méthode est susceptible de mal tourner. Mais, ça n’a rien d’inéluctable : le mot clé throws, indiquant les exceptions lancées par une méthode, marche tout autant pour des exceptions vérifiées que nous vérifiées. Aussi, rien ne vous empêche d’écrire:

public Foo bar() throws BarException{
	// ..
}

avec

public BarException extends RuntimeException {
	// ..
}

Vu que BarException étend RuntimeException, vous ne serez pas obligé de catcher à chaque throw, par contre votre IDE préféré devrait vous indiquer son existence à la moindre occasion.

Par contre, par pitié, n’indiquez pas que vous êtes susceptibles de jeter n’importe quelle exception, genre:

public Bad doBad() throws Throwable

ainsi que la variante

public Bad doBad() throws Exception

En effet, là vous vous contentez d’indiquer que le code est susceptible de mal tourner, d’avoir des bugs et ainsi de suite. Or c’est le cas de tout bout de code. Du coup n’importe quelle application se doit de gérer le cas de l’exception inattendue, celle due à un bug bien idiot. Aussi, en indiquant « throws Throwable », vous ne faites que rappeller l’évidence verbeusement. Maaaal.

Il y a encore pire : avaler l’exception. Vous savez, ce que propose de faire Eclipse par défaut:

} catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

Mais encore plus pire est possible (youpee) :

} catch (Throwable t) {
}

Certes, vous n’aurez plus d’exception dans l’application. Mais celles ci ne sont que les symptômes du mal… Sans elles, le mal sera toujours présent mais en plus indétectable. Plantages et comportements aléatoires en perspective, que du bonheur.

A noter que catch(Throwable t) détient la palme. En effet, pour mémoire, la hiérarchie des exceptions Java est la suivante:

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

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

Tous les problèmes du niveau de la JVM remontent via des sous classes d'Error. Quelques exemples fameux: StackOverflowError, OutOfMemoryError et autres KernelError.

Les problèmes dus au code sont signalés via des classes héritant d'Exception ou de RuntimeException. Genre les IOException ou les ClassCastException.

Du coup, le petit malin qui avale tout à partir de Throwable ne saura notifié ni des erreurs de son code ni des problèmes de la JVM. Sympa non ?

2. Bien allumer la patate lors de sa cuisson

Maintenant qu’on sait comment passer la patate, voyons comment la créer.

En premier lieu, toujours hériter de RuntimeException (sauf pour les développeurs derrière la JVM, bien sûr, qui eux étendent Error).

Pourquoi RuntimeException ? Facile : on veut faciliter la passe de la patate chaude, donc autant que l’appelant n’ait rien à faire par défaut, comme ça au moins il ne risque pas de faire mal. Genre avaler l’exception.

Ensuite, quel est le but de la patate ? Signaler un problème, un dysfonctionnement, une erreur. Du coup, autant communiquer un maximum d’informations : vos exceptions doivent toujours avoir un constructeur avec un message de type String. A chaque création d’une instance on pourra alors donner un maximum d’information de contexte. Genre les paramètres de la méthode appelée. Par contre, le nom de la méthode et la pile d’appel sont là par défaut, dans la pile d’appels (stack trace) pas besoin de dupliquer…

Si jamais votre exception encapsule une autre exception, merci de la lui donner. En effet, que se passe-t-il si je code ainsi:

catch (IOException e){
	throw new MyException("File access failed");
}

C’est tout simple : la personne traitant mon exception ne pourra pas en connaître la cause réelle. Et même dans le cas de cette bonne vieille IOException, si commune, ne vous laissez pas abuser : ces sous classes sont aussi diverses que ErrorInQuitException ou BadAudioHeaderException. Bref, si une exception a pour origine une autre exception, toujours la transmettre.

Bien sûr, si on peut donner du contexte, c’est encore mieux :

catch (IOException e){
	throw new MyException("File access failed for file '" + file + "' and user '" + user + "'", e);
}

In fine, le squelette d’une bonne exception à soi est, à mon humble avis, quelque chose du genre :

public class MyException extends RuntimeException{
	
	public MyException(String message){
		super(message);
	}

	public MyException(String message, Throwable throwable){
		super(message, throwable);
	}

	public MyException(Throwable throwable){
		super(throwable);
	}

} 

Pas de constructeur sans paramètre: on veut nécessairement du contexte. (Bon des fois y en a vraiment pas au delà de la pile d’appels, mais bon, comprenez le message ;)).

Si jamais vous décidez d’ajouter des paramètres supplémentaires et spécifiques à votre application, n’oubliez pas alors de surcharger getMessage(), afin que tout le contexte soit toujours affiché.

3. L’art de bien attraper la patate

A l’occasion, si on sait quoi faire sur l’exception, alors on s’en occupe.

Cela arrive généralement en deux circonstances : soit au plus près de l’exception, quand on peut retomber sur ses pieds autrement, soit plusieurs niveaux d’abstraction plus haut, aux frontières de l’application ou du module. Genre pour afficher un message d’erreur à l’utilisateur, ou au système tiers.

Gérer l’exception au plus près est normalement simple : on ne fait cela que si on sait quoi faire.

Par exemple:

public boolean isValidInteger(String text){
	try {
		Integer.parseInt(text);
		return true;
	} catch (NumberFormatException e){
		return false;
	}
}

Pour la gestion à plus haut niveau, il faut s’assurer d’attraper vraiment toutes les exceptions. Pour cela, il y a depuis Java 5 la méthode Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) qui définit qui sera appelé en dernier recours, si rien n’a attrapé l’exception au préalable. Ainsi toutes les exceptions seront attrapées de façon certaines, qu’elles aient été lancées le thread courant ou un autre. Faites en bon usage.

La gestion de l’erreur implique souvent la communication d’un message à un tiers, que ce soit l’utilisateur ou le système appelant. Il arrive parfois que des actions plus drastiques soient nécessaires, particulièrement dans le cas des Error, où parfois l’arrêt est la seule option (dans le cas d’une OutOfMemoryError, c’est le seul remède que je connaisse par exemple).

Toutefois, qu’on traite l’exception au plus près ou à plus haut niveau, toujours, j’ai bien dit TOUJOURS, faire en sorte de donner tout son contenu au développeur qui devra débugger le code. Ne serait ce qu’un log, fusse-t-il de niveau debug, ou, même, au pire, une sortie console. L’essentiel est que toute l’info soit accessible sans devoir changer le code. Se contenter d’afficher le message de l’exception (sans la pile d’appels) est, par exemple, notoirement insuffisant. De même, si vous utilisez un framework de logging, assurez vous qu’il affiche bien tout le contenu de l’exception, ainsi que des diverses différentes exceptions éventuellement contenues dans l’exception courante. La stack trace complète est de la plus haute importance.

Espérons que ces conseils améliorent votre gameplay à la patate chaude. Bien que souvent négligé et bien moins hype que d’autres jeux plus récents (comme réinventer la roue par exemple), gérer correctement les exceptions est crucial pour toute application, afin d’en assurer l’évolution et la maintenance sereinement.

Au plaisir de vous lire !

NB: pour les utilisateurs d’Eclipse (je sais, ça aussi c’est pas à la mode, désolé), notez qu’il y a une « Java Stack Trace Console » disponible au niveau de la console eclipse, via le bouton « Open Console ». Lorsque vous y collez une stack trace, eclipse vous permet d’aller aux différents points concernés en cliquant sur les lignes de la stack trace. Pratique. Pas révolutionnaire mais pratique.

NB2: Java 7 introduit le multi catch et autres try with resources. Bien que pratiques, ils ne changent pas fondamentalement la donne (sauf pour l’art obscur de la fermeture de ressources, non abordé ici).

Étiquettes : , , ,

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

mai 30, 2013 5 commentaires

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 😉

Étiquettes : , , ,

Book review : Clean code

juillet 16, 2009 Laisser un commentaire

hi

I recently finished Clean code, from Robert C. Martin (well, in fact it’s mainly from him and some guests, but then..).

Anyway, the focus of this book is how to write clean code, meaning readable and maintainable code. For this, the authors go at length on the different aspects, dedicating whole chapters to aspects like « Meaningful Names », »Functions » or « Comments ».

At first glance, these chapters can seem a bit boring, containing mostly « common sense » point. However, all put together, I really have the feeling to understand way better how to write clean code. We all know that methods should be short. But who does so actually ? Now, I try to : the book really convinced me of applying these « rules ». And I really feel that my code is getting more and more readable… Nice feeling, enough, I would say, to recommend reading this book !

However, don’t expect a « perfect book » similar to Effective Java. Indeed, some chapters are either way too superficial, like the one on concurrency (and even the appendix on it), or simply boring : the refactoring examples aren’t compelling at all. They even seem a bit odd, since this book isn’t explicitly about refactoring (more on these kind of books another time lol).

Finally, the last chapter of this book should please every reader of this blog :chapter 17, « Smells and heuristics », provides a summary of many codesmells (summing up most of the book), which are at least nice to have in mind.

Overall, I think this book helped me a lot to improve my « clean » code ability. It’s nice as shiny as patterns or the like, but that’s the code we write and read everyday !

I think as well it’s the kind of book worth being read again after some time… In fact, I was already reading it again recently. I found some interesting points on the « introduce null object pattern ». Even better, some topics covered matched exactly some issues I had encountered just before, providing very good food for the mind ! 😀

++
joseph

Étiquettes : , , , ,
%d blogueurs aiment cette page :