Le demi-cercle (épisode 18 -- Arrangements)

Vivre, c'est passer d'un espace à un autre en essayant le plus possible de ne pas se cogner. — Georges Perec

Jérémie s'installe derrière le clavier. Audrey, Farid et toi, derrière les tables qui font face au mur. Jérémie demande :

Jérémie : - Qu'est-ce qu'on a au programme ?

Tu dis : - Je vous propose de tenter de résoudre le ticket 4244. - Encore lui ? Fait Jérémie, je croyais que c'était résolu. C'est de l'histoire ancienne ! - On a identifié le problème, et livré un correctif en recette. Mais ce que je voudrais, c'est solutionner le problème, c'est à dire : écrire un test unitaire sur la partie du code qui contenait le défaut, au besoin en refactorant, et ensuite réfléchir à la manière dont on aurait pu éviter ce défaut.

Audrey dit : - Il y a un courant d'air ici ? Je trouve qu'il fait froid.

Farid explique : - C'est parce que la porte est ouverte, et que ces tables sont au milieu de la pièce. - C'est vrai que la nouvelle disposition, c'est loin d'être pratique.

Ce n'est pas pratique pour circuler. C'est pratique pour programmer ensemble.

Tu vas fermer la porte du bureau et tu reviens t'asseoir en disant : - Jérémie, est-ce que tu peux ouvrir le code du module Budget ? J'ai mis un Fixme quelque part. - Fixme, fixme… OK.

Audrey scrute le code en fronçant les sourcils. Elle dit : - C'est quoi ce truc ?

Farid explique : - C'est une factory de règles de gestion. Ça permet de générer la bonne règle de calcul pour une situation donnée… - La factory je vois bien, ce que je comprends pas c'est ce qui se passe ensuite. Pourquoi la RG_CalcReport instancie un iDBConnection ?

Farid hésite un instant. - En cas de report d'un précédent annuel, on doit rechercher les montants concernés. Ça nécessite d'attaquer la base de données. - Je croyais qu'on avait un ORM. - Qu'est-ce que tu veux dire ? - Object Relational Mapping. - Ça je connais. Qu'est-ce que tu veux dire en fait ? - C'est n'importe quoi ce code.

Stop.

Tu interviens : - Audrey, qu'est-ce qui te choque dans le code ? - Sérieusement, tu me poses la question ? - Sérieusement. Je veux vraiment savoir. - Alors, par où je commence… - Commence par le sujet : ORM. - Le principe d'un ORM c'est qu'il mappe automatiquement les entités de la base de données sur les objets du modèle. Ce qui nous évite d'avoir à attaquer la base de données nous-mêmes via du SQL.

Farid dit : - Eh bien oui ! Et alors ? - Farid, est-ce que tu vois bien ce que je vois en ligne 425 ?

"SELECT Compte, Pos_Annuelle, fk_Budget, Amount FROM T_BUDGET Where …"

- Je vois bien. C'est volontaire. C'est parce qu'on a pas d'objet spécifique BudgetReportAnnuel, et donc c'est ça ou bien je remonte tous les Budgets de la base…

Jérémie intervient : - Franchement cet ORM, c'était pas une bonne idée au départ. Mais bon. Je dis ça, je dis rien.

Tu reprends : - OK. On n'utilise pas l'ORM pour les services qu'il peut nous rendre. Est-ce que c'est le problème principal ? Qu'est-ce qu'on a d'autre comme problème, en relation avec le bug qui nous occupe ? Jérémie rétorque : - C'est déjà pas mal. - C'est déjà pas mal, mais ce n'est pas quelque chose que l'on peut résoudre en quelques heures, et c'est ce qui me gêne.

Audrey demande : - Tu voulais écrire un test qui échoue à cause du bug. Je propose que l'on fasse cela.

Tout le monde est d'accord. Vous vous y mettez.

C'est encore le même étage, dans le même état qu'il y a deux jours. Rien n'a bougé dans ce mausolée — rien de tangible ou de visible en tout cas. Ce que vous avez déplacé la dernière fois, vous ne pouvez plus le déceler. L'équipe pose son matériel à l'entrée de la pièce principale. L'objectif : ramenez et branchez un groupe électrogène qui vous fournisse en lumière le temps d'opérer en toute sécurité des changements sur la structure. Audrey mène la conversation : - Crée une classe de test. RG_CalcReportTest me va bien. Écris un test qui dit que quand le budget provient d'un exercice précédent, il doit prendre en compte le report annuel. - OK, dit Farid. Il nous faut des données. - Tu veux dire qu'il nous faut un objet BudgetReport. - Admettons. - Oui. Admettons que notre couche ORM marche et qu'on dispose d'un tel objet. Il a un montant, une date, une période.

Jérémie dit : - OK. Je crée la classe au mileu de nulle part. - Ça s'appelle une classe anonyme, Jérémie. - Je taquine. - Ajoute une ligne de commentaire : arrangement. - Voilà. - ensuite, assertion : Le budget initial a été augmenté du montant du report. - OK. - Maintenant, action : tu lances le calcul en utilisant RG_CalcReport.execute.

Tu remarques qu'Audrey a adopté la façon de structurer les tests qui a cours au Dojo.

Jérémie affirme : - Ça ne va pas marcher.

Jérémie finit de mettre au point le test et l'exécute. L'execution s'arrête avec une exception : Data Source unresolved. Jérémie diagnostique : - Il nous faut une connection active à la base de donnée. La connection a échoué. Le code s'exécute normalement dans un contexte où il dispose d'une ressource qui lui indique la source de données. Dans le contexte des tests, il n'a pas cette ressource.

Farid indique : - Il suffit de renseigner la chaîne de connection dans un fichier de ressources.

Tu demandes : - Est-ce qu'on est sûrs de vouloir faire ça ? Je veux dire : se connecter à la base de données à chaque fois que ce test sera exécuté ? - Pourquoi pas ? - Et si demain les données ne sont plus cohérentes pour le test ?

Jérémie remarque : - Pas besoin d'attendre demain, c'est déjà probablement le cas.

Farid : - Qu'est-ce que tu proposes ? - Ton RG_CalcReport demande un IDBconnection. Est-ce qu'on peut lui passer null pour l'instant ?

Jérémie lui passe null et relance le test. Null pointer exception.

Farid demande : - Et maintenant ?

Évidemment.

Dix secondes de silence. Tu demandes : - Jérémie tu as des idées ? - Nope. - Alors reste où tu es. Est-ce qu'on peut lui passer un Mock de la connection ? - On peut peut-être. Mais il faut changer la signature du constructeur. - OK ?

Tu observes tes coéquipiers. Audrey lève le pouce. Farid lève le pouce. Jérémie lève la main d'une façon qui semble vouloir dire "c'est vous qui naviguez", puis change la signature en question. Sur la colonne de droite s'allument 25 petits rectangles rouges.

Facile.

Tu reprends : - Crée un nouveau constructeur, plutôt que de modifier l'ancien. Avec la signature qui va bien. Voilà.

Audrey comprend où tu veux en venir : - Il faut que ce constructeur fasse la même chose que celui d'origine. Extract Method sur le contenu du constructeur d'origine. - Comment je la nomme ? - Initialize. - OK.

Jérémie s'exécute. - Maintenant ton nouveau constructeur assigne son argument DBConnection au champ DBConnection de la classe, et ensuite il appelle Initialize.

Farid conteste : - Oui mais Initialize va réassigner le champ avec une vraie connection.

Pas de problème.

Tu dis : - Change Initialize de façon à ce qu'elle vérifie si le champ est null. Si le champ est null, fait la connection habituelle, s'il n'est pas null, saute cette étape.

Farid hésite : - Euh. Je croyais qu'on ne voulait pas toucher au code existant avant d'avoir des tests ?

Audrey lui répond : - C'est plutôt qu'on n'aura pas de test avant d'avoir touché au code existant. - Oui mais là ça va trop loin. On ne comprend plus rien.

Jérémie interrompt le débat : - C'est pas grave, parce que ça ne résoud pas le problème de toute façon.

Exception : Unable to mock a static method.

What ?

Voilà une heure passée dans l'étage encombré. La tension a monté, avec la nécessité constante de ne rien toucher, et l'obscurité à laquelle on ne se fait pas. Le groupe électrogène est toujours à l'entrée de la pièce. Il y a une discussion pour savoir ce qui vaut la peine d'être déplacé, et quels sont les risques. L'équipe d'installateurs est peut-être venue pour rien.

Audrey lance : - Et si on faisait une pause ?

Tout le monde approuve. Le bureau se vide. Chacun vaque à des buts différents pendant 15 minutes.

Reprise. Tu te proposes de prendre le clavier.

Jérémie indique la marche à suivre : - Le framework de Mock ne va pas nous servir ici. Je propose qu'on fasse un wrapper de l'interface IDBconnection, et qu'ensuite on implémente un mock nous-même.

Tu vois ce qu'il s'agit de faire. Tu demandes : - Quel nom lui donner ? - IDBConnectionWrapper.

Beurk.

Tu t'exécutes. Tu crées l'interface. Tu demandes : - Quelles méthodes y mettre ? - open, close, query. Ça devrait suffire pour l'instant.

Une fois que c'est fait, tu crées une classe MockDBConnection toute neuve, qui implémente le wrapper.

Jérémie enchaîne : - open ne fait rien; close ne fait rien; query devrait juste renvoyer une copie d'une collection qu'on lui donne via un setter. - OK.

Tu mets en place le dispositif. Audrey consulte le schéma de la base de données. Elle dit : - Je me demandes si l'ORM peut mapper des objets sur des vues plutôt que sur des tables.

Jérémie répond : - Pas de vues. Ça ne passera pas en production. - Pourquoi ?

Tu interrompts : - Et maintenant, je fais quoi ?

Jérémie revient à ce qui se passe sur le mur. - Lance le test, pour voir ?

Le test passe.

Woohoo !

Audrey dit : - On est bons. Il reste à refactorer cette classe. Pas de SQL dans les classes métier.

Farid se tient assis sur la table de gauche, les bras croisés. Il intervient : - Ce n'est pas une classe métier. C'est un service. - C'est pareil. - Pour toi c'est pareil. En fait c'est différent.

Tu dis : - Pour l'instant, ce qui compte à mes yeux, c'est qu'on est capable d'écrire un test qui parle de calcul et qui ne parle pas SQL.

Farid se retourne vers toi. Il a l'air passablement en colère. - Et ça change quoi ? Une interface, deux classes, un nouveau constructeur. Tout ça pour quoi ? Pour tester une ligne de code ? Je marche pas. Ça prend un temps fou.

Respiration. Ne discute pas.

Tu dis : - Ça prend un temps fou, aujourd'hui, parce que le code doit être adapté.

Tu as discuté.

Farid explose : - Ah oui ? Adapté comment ? Et il reste combien de lignes de code à ton avis, qui n'ont pas leur test ? On a le métier qui attend une version qui est déjà déclarée en retard, et à la place de s'avancer un peu, qu'est-ce qu'on fait ? On monte des usines à gaz ! Tout ça pour quoi ?

Jérémie veut intervenir : - Farid ! Attends !

Farid sort du bureau.

Un des agents sort de la pièce. La discussion continue dans la pénombre. Il y a maintenant une lumière ambrée minuscule, dans un coin, près de la cage d'ascenseur. Pas de quoi pavoiser. C'est à peine si cela rassure, en fait. C'est l'épuisement. On reprend le fil de ce qui vient de se passer. Ce silence obscur, c'est ce qui nous intoxique. Ce qu'il faudrait, ce sont des échafaudages autour de l'immeuble, un accès par les fenêtres, et des cordons de sécurité. Mais ça prendrait trop de temps. Personne ne veut plus de ce chantier continuel. On devrait raser ce truc. Les agents qui étaient restés sortent de la pièce. La lumière ambrée minuscule reste allumée.

(à suivre) Episodes Précédents : 1 -- Si le code pouvait parler 2 -- Voir / Avancer 3 -- Communication Breakdown 4 -- Driver / Navigator 5 -- Brown Bag Lunch 6 -- Conseils à emporter 7 -- Crise / Opportunité 8 -- Le Cinquième Étage 9 -- Que faire ? 10 -- Soit... Soit... 11 -- Boîtes et Flêches 12 -- Le prochain Copil 13 -- La Faille 14 -- Poussière 15 - L'hypothèse et la Règle 16 - Déplacements 17 - Jouer et ranger