This post is a written transposition of the talk I gave at Nantes’ JUG on November 4th 2013. Since it took place in France, this article is in French.
Ce post est une transposition écrite de ma présentation au Nantes JUG le 4 novembre 2013. Les slides sont disponibles ici.
Le titre de cette présentation peut paraitre un peu barbare mais derrière se cache une déclaration simple : Java est trop compliqué ! Je pense qu’il présente des fonctionnalités qui sont plus dangereuses qu’utiles, qui en perturbent l’apprentissage et l’écriture. Je suis programmeur auto-didacte depuis une dizaine d’années, et je n’ai rejoint que récemment l’industrie, au sein d’une grande SSII. Bien que j’ai été prévenu que la qualité du code dans l’industrie était mauvaise, j’ai tout de même été très surpris de la réalité. Pourtant, le projet auquel je participe a, sur le papier, tout pour réussir : des tests unitaires à foison, des tests bout-en-bout en veux-tu en voilà, une plate-forme d’intégration continue avec qualimétrie quotidienne, et un système de revue de code. Magnifique n’est-ce pas ? Eh bien non, car la qualité du code reste assez mauvaise.
Qu’est-ce que j’entends par qualité du code ? J’ai cherché une définition objective de la qualité dans le cadre d’une SSII comme la mienne, c’est-à-dire que je me suis demandé quel est l’objectif de l’aventure. Le but de la SSII est de répondre aux besoins du client rapidement pour pas cher. Ainsi elle sera capable de générer des marges et d’être attractive auprès des clients. La caractéristique technique du code qui permet d’atteindre ces objectifs est la maintenabilité. J’émets donc l’hypothèse que rien n’est plus important que la maintenabilité. Un code maintenable sera rapide et pas cher à faire évoluer, par définition. Il reste à obtenir cette maintenabilité. La solution est la modularité. Lorsqu’un système est modulaire, c’est-à-dire qu’il est découpable en parties autonomes, alors il devient possible de travailler sur un seul module est s’abstrayant des autres. L’application est ainsi plus facile à comprendre. Grâce aux frontières bien dessinées des modules, les évolutions seront cantonnées à une partie du système plus petite. Mais la modularité n’est pas triviale à implémenter. Pour nous aider, nous avons premièrement l’encapsulation, qui permet de fermer hermétiquement les modules. Mais surtout, nous avons nos tests. En effet, si un morceau de code est testable unitairement et simplement, alors c’est qu’il est instanciable indépendamment du système : c’est un module. Ce principe doit être appliqué à tous les niveaux (tests unitaires, d’intégration, d’acceptance, systèmes). Pour encore améliorer la maintenabilité, c’est encore mieux de s’assurer que les tests sont d’une extrême lisibilité, précis et concis. Il servent alors de spécification des modules. C’est pourquoi tout au long de cet article je mettrais un accent particulier sur la testabilité du code comme garant de la qualité.
Maintenant que la qualité est définie, je vais définir un autre mot du titre qu’est langage. Cet article se penche en particulier sur Java, puisque c’est le langage que “tout le monde” connait, mais la plupart sinon toutes les remarques qui seront faites sont applicables aux langages orientés classe. Ces langages présentent des fonctionnalités qui me semblent plus dangereuses qu’utiles. Je vais les présenter et expliquer pourquoi je pense que ces langages seraient mieux sans.
findFirstPersonWithName(String)
qui renvoie null si aucun résultat est un exemple commun) qui devient impossible à décoder une fois sorti du morceau de code qui l’a produit (que signifie le null dans la variable person
3 appel de méthode plus loin ?).Article de ce blog sur le sujet : No Nulls Shall Pass
Les schémas sur les slides peuvent aider à comprendre.
Prenons le cas d’utilisation des méthodes privées. Nous avons une classe avec une méthode publique est trop imposante et nous souhaitons la découper en plusieurs morceaux. Pour cela, nous créons deux méthodes privées, que la méthode publique appellent l’une après l’autre et nous partageons le code entre les deux. Il semblerait que nous avons gagné en lisibilité puisque nous avons des unités de code plus petites et nous avons pu nommer deux nouveau morceaux de code. Il subsiste cependant des problèmes majeurs.
Une solution bien meilleure consiste à exporter les méthodes privées vers de nouvelles classes, où elles sont publiques. La ou les nouvelles classes deviennent alors des dépendances de la classe d’origine (des objets de type correspondant lui sont passés par son contructeur).
Article de ce blog sur le sujet : Why I Avoid Private Methods
Une composition, tout simplement ! Voir les slides et Single Class Inheritance Is Hell.
Les classes abstraites sont bizarres. Très, très bizarre. La testabilité requiert que les dépendances soient explicites et remplaçables. Les classes abstraites sont tout le contraire. Impossible de les instancier sans une classe fille, et impossible d’instancier la classe fille sans classe mère. Une merveille de non-sens. Alternative : à nouveau une simple composition. Voir les slides et Single Class Inheritance Is Hell.
new
) est une dépendance non remplaçable, ce qui rend le code qui l’utilise très difficile à tester.Avant :
class SomeDomainClass {
public void sayHello() {
System.out.println("hello!");
}
}
Après :
class SomeDomainClass { // pure domain class
private final Printer printer;
public SomeDomainClass(Printer printer) {
this.printer = printer;
}
public void sayHello() {
printer.println("hello!");
}
}
class SystemPrinter implements Printer { // adapter class
public void println(String message) {
System.out.println(message);
}
}
import static
sont extrêmement utiles pour écrire des tests très lisibles. Comme on ne teste pas les tests, ça pose moins de problèmes.Article de ce blog sur le sujet : Classes as Simple Type Definitions
Ma référence en la matière : Misko Hevery’s Guide to Writing Testable Code
Je ne dis pas qu’il faut absolument et complètement banir toutes ces fonctionnalités. Il faut simplement reconnaitre et maitriser leurs inconvénients. On peut alors peser le pour et le contre et prendre de sages décisions. Les ennuis commencent quand on a affaire a des débutants. Les problèmes que j’ai exposé sont rarement (jamais ?) abordés lors de la formation des programmeurs, que ce soit à l’école ou dans les livres d’introduction. Au contraire, des fonctionnalités comme l’héritage de classe sont présentées comme héroïques, indispensables, à la base de l’orienté-objet ! Il n’en est rien. Et pourtant, les SSII demandent tous les jours à des débutants de prendre des décisions concernant le design de code. Il y a manifestement un sérieux problème au niveau de la formation des développeurs. C’est peut-être un des seuls métiers où l’on est traité comme compétent avant d’être sorti de l’école.
Article de ce blog à ce sujet : Pragmatism & Beginners