Refactoring sécurisé par property-based testing
Le scénario est classique : une fonction legacy , enchevêtrée, difficile à maintenir, mais dont le comportement est correct (ou du moins accepté comme tel par les utilisateurs du système).
On souhaite la réécrire en une version plus propre, plus performante, mieux structurée. Le risque est évident : introduire une régression subtile, un cas limite oublié, une divergence silencieuse.
L’approche traditionnelle consiste à écrire des tests unitaires couvrant les cas connus, mais comment s’assurer qu’on n’a pas omis un cas ? Le property-based testing offre une garantie bien plus forte : vérifier que pour des milliers de valeurs générées aléatoirement.
Le code legacy comme oracle
Cette propriété, à savoir l’équivalence entre l’ancienne et la nouvelle implémentation, est d’une simplicité désarmante à énoncer.
On ne décrit pas ce que la fonction devrait faire, on affirme simplement que la nouvelle version fait exactement la même chose que l’ancienne. Le code legacy devient son propre oracle, sa propre spécification exécutable. C’est une extraction de connaissance : le comportement de l’ancien code est la spécification, même quand plus personne ne se souvient pourquoi il fonctionne ainsi.
La librairie de PBT génère des entrées arbitraires conformes au domaine de la fonction, les soumet aux deux implémentations, et vérifie l’égalité des résultats. Si une divergence apparaît, le shrinking identifie l’entrée minimale qui distingue les deux comportements : ce sera un cas de test précis révélant exactement où la nouvelle implémentation dévie.
La qualité des générateurs
La mise en œuvre exige quelques précautions.
Il faut d’abord définir des générateurs qui produisent des entrées représentatives du domaine réel de la fonction :
- Une fonction travaillant sur des utilisateurs nécessite un générateur de structures utilisateur
- Une fonction manipulant des arbres nécessite un générateur d’arbres valides
La qualité du refactoring vérifié dépend directement de la qualité des générateurs : des entrées trop uniformes manqueront les cas limites, des entrées mal formées testeront un domaine que la fonction ne rencontre jamais en production.
L’analyse des données réelles peut guider la conception de générateurs pertinents.
Définir l’égalité
L’égalité elle-même mérite attention :
- Pour des valeurs primitives,
===suffit - Pour des structures complexes, il faut une égalité structurelle
- Pour des résultats flottants, une égalité approximative avec epsilon
- Pour des fonctions retournant des effets, il faut comparer les effets produits ou les valeurs finales après interprétation
Si la fonction originale a des effets de bord (mutation d’un état global, écriture de fichier), il faut soit les isoler, soit capturer et comparer les traces d’effets.
Ces complications reflètent les difficultés inhérentes au code legacy ; le PBT ne les fait pas disparaître, il les rend explicites.
Un filet de sécurité pour refactorer avec audace
Cette technique constitue un filet de sécurité remarquablement efficace pour les refactorings de préservation de comportement.
On peut restructurer le code avec audace (extraire des fonctions, introduire des abstractions, changer les structures de données internes) tout en vérifiant continuellement que le comportement observable reste identique.
Chaque exécution du test explore un vaste espace d’entrées, bien au-delà de ce qu’un humain pourrait imaginer.
Quand le refactoring est terminé, quand a prouvé son équivalence avec sur des milliers de cas, on peut supprimer avec une relative confiance. Le code legacy a servi une dernière fois : comme spécification de son propre remplacement.
Cette approche prolonge la durée de vie des actifs logiciels. Ce codebase de dix ans n’a pas besoin d’être jeté : il peut être amélioré pièce par pièce, en confiance. Les réécritures complètes sont coûteuses et risquées ; le refactoring incrémental sécurisé par PBT est économique et sûr.
Envie d'approfondir ces sujets ?
Nous aidons les équipes à adopter ces pratiques via du conseil et de la formation.
ou écrivez-nous à contact@evryg.com