Vous avez sûrement déjà rencontré l'une de ces classes monstrueuses, qui totalisent des milliers, voire des dizaines de milliers de lignes de code. Elles réalisent tant de choses différentes qu'elles en sont presque omnipotentes, et à coup sûr, c'est une horreur de les maintenir : on parle souvent de code spaghetti, on les appelle aussi des objets divins, et ils sont synonymes de dette technique. Dès lors, remanier une telle quantité de code est un exercice périlleux, car une erreur introduite par mégarde peut avoir un impact sur une multitude d'autres objets qui dépendent à un moment donné de cet objet divin.
Dans un tel contexte, la méthode des développements pilotés par les tests constitue un pivot incontournable, dans le passage d'un objet divin à une conception orientée objet digne de ce nom.
Voici une approche pragmatique qui a fait ses preuves, c'est-à-dire le zéro bogue après refonte :
- Créer un cas de test pour chaque fonctionnalité.
- Instancier l'objet à tester.
- Développer des objets bouchons (mock objects) pour chaque dépendance.
- Initialiser le contexte (état des attributs, paramètres d'entrée, etc).
- Copier-coller, dans la méthode de test, la partie du code source de l'objet divin à remanier dans le cas de test.
- Pour chaque instruction, écrire les assertions qui vont permettre de contrôler les résultats obtenus. Chaque résultat attendu est déclaré avec les attributs du cas de test : c'est ce qu'on appelle en Anglais les "fixtures" (fournitures).
- Si le résultat attendu doit être obtenu en fonction de paramètres d'entrée, il faut initialiser ces paramètres d'entrée dans la phase de préparation du test (setup), en plaçant un commentaire comportant le nom du résultat attendu concerné par ces paramètres.
- Commenter le code provenant de l'objet divin, qui a servi de guide pour l'écriture des assertions.
- Quand c'est terminé, le test doit passer. S'il est bloqué, corriger les omissions.
A partir de ce travail préalable, on peut commencer à remanier la partie du code de l'objet divin couverte par le cas de test (test de non-régression) que l'on vient d'écrire, et entamer une vraie conception Objet, avec de nouvelles interfaces et classes reliées entre elles par des liens sémantiques de responsabilité et de collaboration. Je devrais parler de conception émergente, car elle est susceptible d'apparaître tandis qu'on écrit les tests unitaires couvrant les futurs objets métier à implémenter. Après l'écriture de ces tests, on peut enfin en toute sécurité distribuer le code source de l'objet divin de départ dans ces nouveaux objets.
Finalement, l'objet divin devient une façade pour l'accès aux services de la couche applicative qu'il incarnait (presque) à lui tout seul, tandis qu'il délègue la responsabilité de ces services aux objets spécifiques.