mercredi 8 juin 2011

Refactoring d'Objet divin

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 :
  1. Créer un cas de test pour chaque fonctionnalité.
  2. Instancier l'objet à tester.
  3. Développer des objets bouchons (mock objects) pour chaque dépendance.
  4. Initialiser le contexte (état des attributs, paramètres d'entrée, etc).
  5. Copier-coller, dans la méthode de test, la partie du code source de l'objet divin à remanier dans le cas de test.
  6. 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).
  7. 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.
  8. Commenter le code provenant de l'objet divin, qui a servi de guide pour l'écriture des assertions.
  9. 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.

jeudi 10 mars 2011

Principe ouvert/fermé

En programmation orientée objet, le principe ouvert/fermé signifie qu'une entité logicielle (classes, modules, fonctions, etc) devrait être ouverte pour extension, mais fermée pour modification. L'idée est que l'on puisse modifier le comportement d'une entité, sans en changer le code source.

Ce principe est particulièrement bien adapté à des environnements de production critiques, où tout changement du code source doit être approuvé par un processus jalonné par des revues de code, des tests unitaires, une phase de qualification, ... En effet, si modifier le code existant implique de détecter d'éventuelles régressions par une batterie de contrôles portant à la fois sur les anciennes et les nouvelles fonctionnalités, au contraire l'étendre peut réduire considérablement l'effort de qualification, puisque ce dernier ne portera que sur les nouveautés.

Depuis son apparition en 1988 dans l'ouvrage Object Oriented Software Construction, l'application de ce principe a largement évolué, et gagné en souplesse.

Principe ouvert/fermé selon Meyer :
On attribue généralement ce concept à Bertrand Meyer, l'auteur de Object Oriented Software Construction :
  • L'implémentation d'une classe en production ne doit pas être modifiée, sauf correction d'anomalies.
  • Ajouter ou changer une fonctionnalité implique de créer une nouvelle classe.
  • La nouvelle classe peut réutiliser le code de la classe existante, par héritage.
  • On est libre d'exposer la nouvelle implémentation par une nouvelle interface ou par l'interface existante.
D'après le principe de Meyer, une implémentation existante peut être réutilisée par héritage, alors que 2 interfaces ne devraient pas hériter l'une de l'autre.

Principe ouvert/fermé par polymorphisme :
Puis, entre 1996 et 2001, plusieurs personnes contribuent à redéfinir le principe ouvert/fermé en tirant profit de l'abstraction et du polymorphisme des langages objet :
  • Le comportement d'une entité peut reposer sur de multiples implémentations différentes.
  • Les implémentations adoptent toutes la même interface, ou héritent de la même classe de base abstraite.
  • L'interface (ou classe abstraite) existante ne peut pas être modifiée.
  • Ajouter ou changer une fonctionnalité implique de créer une nouvelle interface (ou classe abstraite).
  • Les implémentations peuvent être modifiées.
  • Les implémentations peuvent se substituer les unes aux autres par polymorphisme.
D'après le principe par polymorphisme, une interface (ou classe abstraite) existante peut être réutilisée par héritage, alors que 2 implémentations ne devraient pas hériter l'une de l'autre.

Synthèse et comparaison :

OCP selon MeyerOCP par Polymorphisme
Extension par héritage dela classe d'implémentationl'interface existante
Constantes entre 2 évolutionsimplémentation et interfaceinterface seulement
Modification possible pour-implémentations

On constate donc que le principe ouvert/fermé a évolué vers un niveau d'abstraction plus important, ainsi qu'une plus grande flexibilité. Une bonne illustration de ce principe est le design pattern Visiteur de la Guilde des Quatre.

Références :

mercredi 9 mars 2011

SOLID, 5 concepts orientés objet

Dans la conception d'application orientée objet, SOLID est un acronyme introduit par Robert Cecil Martin (alias "Oncle Bob") au début des années 2000. SOLID est la réunion de 5 concepts ou principes en programmation orientée objet. Appliqués ensemble, ils sont susceptibles de permettre de créer des systèmes logiciels plus faciles à maintenir et à faire évoluer. SOLID fait partie des pratiques agiles d'ingénierie logicielle, et peut être un complément puissant à la méthode de pilotage par les tests (Test-Driven Development).


Les 5 principes repris par SOLID sont :
  • S pour SRP, Single Responsibility Principle (principe de la responsabilité unique) :
    Un objet ne devrait avoir qu'une seule responsabilité.
  • O pour OCP, Open/Closed Principle (principe ouvert/fermé) :
    Une entité logicielle, telle que classes, modules, fonctions, etc, devrait être ouverte pour extension, mais fermée pour modification.
  • L pour LSP, Liskov Substitution Principle (principe de substitution de Liskov) :
    Il doit être possible de remplacer un objet par n'importe quelle instance de ses types dérivés, sans que cela affecte le reste de la fonction qui l'utilise.
  • I pour ISP, Interface Segregation Principle (principe de ségrégation des interfaces) :
    Mieux vaut avoir autant d'interfaces que de clients, plutôt qu'une interface à vocation générale.
    Autrement dit, les clients d'une interface ont besoin de connaître de cette interface seulement les méthodes qu'ils utilisent. Inversement, un client ne doit pas dépendre d'une interface dont il n'utilise pas une partie.
  • D pour DIP, Dependency Inversion Principle (principe d'inversion des dépendances) :
    L'inversion des dépendances s'énonce en 2 points :
    1. Des modules haut niveau ne devraient pas dépendre de modules bas niveau et des détails de leur implémentation. Tous devraient dépendre d'abstractions.
    2. Les abstractions ne devraient pas dépendre des détails, mais les détails devraient dépendre des abstractions.
    Exemple : L'Injection de Dépendances (Inversion of Control) est une méthode qui applique ce principe.
Références :