Refactoring : la technique du Golden Master
Il fait beau, il fait frais ce matin, vous avez bien dormi et vous vous sentez d'attaque pour refactorer. Noble intention !
Il y a quelques refactorings sûrs, c'est-à-dire des modifications basées sur l'arbre syntaxique abstrait (AST). Ce sont des "low hanging fruits" qu'il faut cueillir, à supposer toutefois qu'un système de typage soit en place — friendly poke aux aficionados du JavaScript, parce que "le typage, c'est trop contraignant" 🤣
En dehors de ça… oui, ça nous démange de séparer les responsabilités de cette fonction/classe qui fait tout ("God class").
Mais là, on rentre d'emblée dans la 🚨 ZONE DE DANGER 🚨
Car derrière chaque action se cache une régression, tapie dans l'ombre, prête à bondir et qui ricane à l'idée de faire tomber la prod 😈👻
Comment les éviter ? Ça serait bien d'avoir un filet de sécurité. Et ça s'appelle ? Des tests, exactement.
Fort bien, écrivons des tests, alors.
Sauf que ça n'est pas si simple : le code n'a pas été pensé pour être testable. Comment lire la valeur de cet état s'il n'est pas exposé ? Comment mocker ce service ? Faire de l'injection de dépendances ? Rien n'est en place pour ça.
Alors comment faire ?
La bonne nouvelle, c'est qu'on ne veut surtout pas coupler les tests à l'implémentation, puisque celle-ci va changer. Nous souhaitons des tests qui reflètent le comportement, pas l'implémentation.
Comment "capturer" le comportement de l'application ?
Une façon de faire consiste à parsemer le code de logs fonctionnels. Par exemple, "Le solde du compte bancaire de Paul est de 1400€ au 31/10/2022" plutôt que { id: 171932, name: "Paul", balance: 1400, unit: "EUR", date: "31/10/2022"}
— modélisation par ailleurs hautement contestable.
Et là, nous venons de réinventer le Golden Master, qui procède en 3 temps :
-
Temps #1 : création du master, c'est-à-dire des logs fonctionnels capturant le comportement de l'application de manière exhaustive. Cette étape requiert généralement plusieurs itérations, car elle est en même temps une phase de découverte de l'application. Durant cette phase, on s'aidera de la couverture de code, que l'on veut à 100%. Mais attention, la condition est nécessaire sans être suffisante : on peut avoir couvert le code à 100% par des tests sans pour autant avoir exploré tous les chemins d'exécution de l'application.
-
Temps #2 : une succession de refactorings, aussi petits que possible (voir à ce sujet la Méthode Mikado). A l'issue de chacun, on rejoue l'ensemble des tests. Les logs fonctionnels ainsi générés sont comparés au master. La moindre différence est le signe d'une régression. Durant cette étape, il n'est pas interdit d'écrire des tests propres à la nouvelle implémentation, voire de se laisser guider par les tests (Test-driven development). Comprenez : "Faites-le !" 😁
-
Temps #3 : les tests du Golden Master sont par essence temporaires. Ils doivent être vus comme des échafaudages, que l'on démonte une fois le bâtiment construit. Nous avons pris soin de rajouter des tests unitaires et d'intégration pour la nouvelle implémentation, donc nous pouvons supprimer les tests du Golden Master sans rien perdre en couverture de code.
Voilà. Le Golden Master, c'est ça. Rien que ça, au fond, mais encore faut-il le faire. Et cela demande de la pratique, alors… à vos claviers !