La mémoire en java, expliquée par votre banquier

DevOps
« Objet », « variable », « référence »... des mots différents pour exprimer la même chose ? Pas vraiment… Et la méconnaissance peut coûter cher !

Vous pratiquez le Java depuis des mois, voire des années. Vous surfez sans trop de difficulté sur chaque nouveau concept haut-niveau qui se présente : hibernate, annotations, lambda, programmation orientée concept ou autres librairies disponibles en Java. Mais sauriez-vous reconnaître un des pièges les plus répandus et les plus fondamentaux de Java ? Nous vous expliquons :

Regardons ce test unitaire

Il est simple, facile et semble bien fonctionnel. Nous faisons volontairement l’impasse sur l’utilisation peu « orientée objet » qu’il implique, puisque nous avons une seule classe bien trop générique pour tous les aliments possibles, mais c’est pour l’exemple. 

Seulement voilà, ce code a un gros défaut, il est incorrect. 

Plus exactement, il a des chances d’être systématiquement « vrai » même lorsqu’il ne devrait plus l’être. En bref, il est dangereux, parce qu’il présuppose que le code de « presserAliment » n’est pas trop mal écrit, un comble pour un test unitaire non ?


Si vous émettez des doutes sur le pourquoi du comment, cet article est fait pour vous ! 

Le banquier

En tant que client, lorsque vous allez voir votre banque pour demander l’ouverture d’un compte, vous lui en donnez les caractéristiques en choisissant parmi les prestations qui s'offrent à vous : PEL, livret A, etc. 


Votre conseiller bancaire, lui, s’occupe des démarches à suivre. Une fois votre compte créé, il vous donne un RIB que vous imprimez sur un papier, afin de vous permettre de réaliser plus tard des opérations dessus.

Remplaçons notre banquier par la JVM

Eh bien, la mémoire en Java, c'est exactement ça.

En tant que code Java, lorsque vous allez voir votre JVM (Java Virtual Machine) pour demander la création/instanciation d’un objet, vous lui en donnez la classe et elle s’occupe du reste.

Une fois votre objet créé, elle vous donne une référence que vous affectez (avec « = ») dans une variable, afin de vous permettre de réaliser des opérations dessus plus tard.

Avec cette image, ça devient facile à retenir et on ne peut plus jamais se tromper. Les variables ne contiennent jamais les objets directement, mais uniquement leurs références.


Pour conserver notre exemple, si une personne photocopie mon RIB sur un autre papier, c'est à dire qu'il copie ma variable dans une autre variable, cette autre personne agira tout autant que moi sur le même compte, c’est à dire sur le même objet.

La seule différence entre le banquier et la JVM est que celle-ci a le pouvoir extralucide de connaître l’existence de tous les RIB ou de toutes les variables existantes à chaque instant.

En effet, si plus personne ne connaît un compte bancaire, c'est à dire que plus personne n’a de variable référente à ces objets, la JVM va pouvoir s'en débarrasser afin de libérer de la mémoire.

La copie de référence

Alors revenons-en à notre Test Unitaire du début. Imaginons que le code de la fonction testée soit le suivant :

Ici, nous comprenons le problème : la variable locale « unJus » est une copie de la variable « unAliment ». Par conséquent, elles désignent toutes les deux un seul et même objet.

Le test unitaire, en faisant « assertEquals » entre l’objet d’entrée et l'objet de sortie, ne fait que comparer un objet à lui-même. Il ne verra donc jamais la modification de l'ingrédient.  

Nous avons ici un « faux positif », en effet, le test est toujours OK, mais il devrait en réalité être KO.

Le cas présenté ici est trivial, il y a clairement un mauvais codage, mais ce type de conception de code est malheureusement encore trop répandu, y compris dans les plus grandes sociétés. 


Encore une fois, un test unitaire ne devrait jamais faire confiance sur la manière dont pourra être écrit ou réécrit le code qu’il teste. Autrement dit, ne faites pas votre test unitaire en fonction du code réalisé mais bien du résultat attendu. 

Pour s’en prémunir, le test unitaire aurait dû vérifier directement la valeur en dur, ou avec un immutable, plutôt que de comparer à la variable d’entrée, comme nous le voyons dans l'exemple ci-dessous. 

Désormais, lorsque vous verrez un code du type « variableA = variableB; », posez-vous toujours la question de savoir si cela est normal ou pas et quel problème cela peut cacher.


De même, chaque fois qu’une méthode retourne un objet, en particulier lorsqu’il est du même type que l’un de ces paramètres d’entrée, vérifiez que ce retour est correct, nécessaire et correctement utilisé par les codes appelants.

Enfin, souvenez-vous que vous ne dupliquez pas votre compte bancaire quand vous photocopiez votre RIB, seules les banques peuvent créer de l'argent.


La duplication de la chaîne « Raisin » dans le dernier exemple vous dérange ? Vous préféreriez sans ? Nous sommes bien d’accord, et ça tombe bien, puisque la classe String est immutable. De par son implémentation, il n’est pas possible de modifier le texte contenu dans un objet String, on peut seulement en créer de nouveaux.


Alors ne vous privez pas. Créez un « String ingredient = "Raisin"; » à utiliser dans la variable d’entrée et également pour le test en sortie. Si vous avez un doute sur le principe des immutables, n’attendez pas le prochain article pour réviser les fondations.


Eric,

Expert technique