Refactoring : la technique du Golden Master

Gold foil background
Photo by Alexander Grey on Unsplash

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 😈👻.

Mouhahahhahaaaa

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, c'est galère.

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 foireuse s'il en est.

Et là, nous venons de réinventer le Golden Master, qui procède en 3 temps. Regardons ça ensemble de plus près 🔎 :

  • 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 (1). 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 (2). 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, d'ailleurs, mais encore faut-il le faire. Et cela demande de la pratique, alors… à vos claviers !

Happy refactoring et à la semaine prochaine 👋 !

(1) Avec la question caractéristique du refactoring : "Bug or feature?". Mais ce sera l'objet d'un article à part entière.

(2) Nous explorerons dans un prochain article comment se "frayer" un chemin dans le code, à l'aide de la méthode Mikado.

(3) Oui, donc au cas-où vous n'auriez pas compris, il y aura d'autres articles 😋

Subscribe to Mathieu Eveillard

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe