Les bonnes et les mauvaises critiques sur Java — Épisode 1 : Les mauvaises critiques

Ouais, je fais pas dans la dentelle.

Aujourd’hui je commence une série qui dissertera, de mon point de vue, des critiques couramment portées sur Java et des vrais problèmes du langage.

Avec l’ami Chocobozzz, nous sommes à peu près d’accord pour dire que Java est un mauvais langage. Cependant, notre désaccord porte sur la nature de ces problèmes. La plupart de ses critiques pourraient être résumées avec la BD suivante :

java

Bien que cette BD soit hilarante (essentiellement parce que j’aime le troll), ces critiques sont malheureusement hors de propos. En premier lieu, parce qu’elles portent sur la technique de la semi-compilation interprétée dans une VM et non sur le langage lui-même. De fait, c’est un reproche que l’on pourrait étendre à tout langage qui n’est pas purement compilé : C♯, Groovy, Scala, Kotlin, Python, Jython, Ruby, JRuby, PHP, OCamL, Perl, JavaScript, Lua, etc.

Mais surtout, parce que ces critiques sont aujourd’hui totalement fausses. Nous ne sommes plus à l’époque des applets Java dégueulasses exécutés dans la navigateur et qui mettaient la machine à genou. Et de toutes façons, tout le monde a fini par se rendre compte que c’était une idée à la con. 1 Les techniques de virtualisation ont fait d’énormes progrès et le matériel informatique aussi. N’importe quelle machine d’entrée de gamme est aujourd’hui capable de faire tourner un programme Java — même codé avec les pieds 2 — à peu près correctement. Il suffit d’ouvrir IntelliJ ou même Eclipse (si, si ! Même Eclipse !) sur un PC moyen pour se rendre compte que ça marche globalement bien. Certes, ça mangera plus de mémoire et ça sera plus long qu’un équivalent en C++ mais bon, vous être pas en train de lancer une fusée dans l’espace, si !?

Donc, la première fausse critique concernant Java, je viens de vous l’exposer, c’est la lenteur de la VM. Et ça n’est malheureusement pas la seule qui concerne plus la VM que le langage. L’autre grosse fausse critique, c’est :

Le garbage collector

Ouep. Et ça va du presque pertinent au plus stupide, ici.

Ce qu’en dit Chocobozz sur le sujet est effectivement assez vrai : avoir un agent d’entretient est intéressant. Sauf lorsqu’il fait son boulot n’importe comment et, en particulier, en mode batch. Dès lors, lorsqu’il se pointe pour nettoyer alors que ça fait 2 heures qu’il branle rien, il va mettre la machine à genou.

Ce n’est pas complètement faux. Sauf qu’encore une fois, c’est un défaut qui est inhérent à la technique de la VM et pas au langage lui-même. C♯ pourrait présenter le même problème. Mais surtout, il convient de nuancer le propos : comme dit en intro, les algos de garbage collector ont fait de sacrés progrès depuis Java 1. Les temps où le GC se pointait pour faire le ménage et mettait tout en pause pendant 10 minutes sont assez révolus.

Pour le plus stupide, j’ai pu lire une personne expliquant qu’en perdant l’habitude de détruire les objets eux-mêmes, les développeurs Java devenaient peu soigneux et paresseux. Comment expliquer à quel point c’est con ? Ah, oui :

implied-facepalm

Premièrement, les devs Java ne sont ni négligeants ni paresseux. En fait, on dirait même qu’ils adorent se rajouter du travail. La moindre petite portion de code est un prétexte pour coller des design patterns dans tous les sens, même en levrette ; j’y reviendrai. Certes, la plupart de ceux-ci sont utilisés de manière stupide (au pif, le builder) et ça rend le code Java sale. Mais pas par manque de rigueur. Par, au contraire, un trop-plein de rigueur.

Quant à s’imaginer que les devs Java deviennent mauvais parce qu’ils n’ont plus besoin de détruire des objets… Si c’est le cas de certains, je dirais que Java n’est pas spécialement la cause ; ils devaient déjà être mauvais à la base. Savoir s’adapter à un langage lorsqu’il en change fait partie des compétences d’un bon dev. Par exemple, j’ai la malchance d’être dev Java depuis 2 ans. Ça ne m’empêche pas de savoir détruire proprement mes objets quand je retourne au C++…

De plus, si la nécessité de détruire des objets produisait des devs plus rigoureux, ça se saurait. La quantité de mauvais code est probablement aussi importante dans les projets C++ que dans les projets Java…

L’absence de gestion manuelle de la mémoire

C’est lié au point précédent, en fait. En très gros : on peut pas allouer manuellement et y’a pas de pointeurs. Je vais être clair et franc sur ce point : je considère d’emblée toute personne me disant ceci comme un irrécupérable mauvais développeur. Sans lui laisser une seule chance. Vouloir à tout prix gérer manuellement sa mémoire, pour moi, c’est une pratique de script kiddie. Ni plus, ni moins. Ça ne sert qu’à se donner un genre et les gens qui le souhaitent ne savent généralement pas de quoi ils parlent.

Il est extrêmement rare d’avoir réellement besoin de soi-même gérer la mémoire dans un projet. Les cas les plus notables sont le développement de noyau de système d’exploit’, de pilotes de périphériques (et encore), et d’applications critiques temps réel (comme le traitement de signaux électroniques, acoustiques ou le jeu-vidéo). C’est tout. Pour tout le reste, gérer sa mémoire manuellement n’a strictement aucun intérêt ; ça sert à que dalle. Si quelqu’un affirme avoir besoin d’avoir la main sur la mémoire système en développant une application bureautique, il ou elle peut s’en retourner réapprendre la programmation (compris, Chocobozz ? :D)

De plus, j’aimerais indiquer qu’il est aussi possible de gérer directement sa mémoire en Java, et que C++ peut simuler un GC. Oui, oui…

En Java, la classe qui s’occupe de la gestion de la mémoire s’appelle Unsafe. La doc est , si ça vous fait marrer. Quant à C++, en allouant ses objets sur la pile plutôt que sur le tas, on obtient un mécanisme équivalent à un GC. La pile étant gérée directement par le système d’exploit’, les objets sont automatiquement détruits une fois le bloc de code qui les contient exécuté. Il est d’ailleurs impossible de détruire manuellement un objet alloué sur la pile. Pour ceux qui savent pas, la pile est l’espace mémoire pour l’exécution du programme. C’est notamment là que sont stockées les variables temporaires échangées par les fonctions (paramètres d’appel, valeurs de retour,etc.)

L’absence de destructeur

Il y a malgré tout un problème de ce côté-là et c’est lié à la gestion de la mémoire. Ça n’est pas souvent, mais il arrive d’avoir besoin d’effectuer une action spécifiquement quand un objet se détruit. Le cas le plus courant, c’est les opérations d’entrée/sortie : supprimer un fichier, sauvegarder des configurations, etc. En théorie, il y a bien la méthode finalize()  qui est appelée avant la destruction de l’objet. En pratique, il n’y a aucune garantie que cette méthode soit exécutée avant la fermeture du programme car le GC n’est pas systématiquement appelé lorsque l’application se ferme. Donc en fait, cette méthode ne sert à rien…

De la même manière, pouvoir détruire manuellement un objet serait pratique pour gérer certains cas d’utilisation : refermer un fichier une fois lu ou libérer une piscine de connexions réseau au plus tôt, etc. À la place, cela nécessite généralement deux opérations : appeler une méthode dispose() , finalize()  ou close()  puis affecter le pointeur à null  pour que l’objet soit récupéré par le GC à sa prochaine exécution…

L’auteur du livre Effective Java et ingénieur développeur Java reconnaît lui-même dans son bouquin que les finalizers sont une mauvaise idée. Il y a même une section du bouquin appelée Avoid finalizers (évitez les finalizers) où il dit :

Finalizers are unpredictable, often dangerous, and generally unnecessary. Their use can cause erratic behavior, poor performance, and portability problems.

C’est quand-même grave, à ce stade de conseiller d’éviter certaines parties du langage…

 

À suivre…

Notes de bas de page :

  1. Sauf l’État. Mais l’État en a 577 sacs d’idées à la con.
  2. ce qui est une constante chez les devs Java