Blog Arolla

Les principes SOLID dans la vie de tous les jours

Cet article est une traduction d'un post de Erik Dietrich donnant quelques moyens mnémotechniques pour se rappeler les bons principes de développement. La version originale est parue ici : visualization mnemonics for software principles.

Introduction

Vous aimez participer aux discussions techniques de vos collègues sur le développement logiciel, mais trop souvent, il vous faut regarder votre téléphone en cachette pour comprendre tous les concepts de la discussion. Ou alors peut-être avez-vous réussi à obtenir un entretien pour un poste de leader technique. Ce qui est sûr, c'est que vous aimez comprendre votre environnement de travail et faire votre métier le mieux possible. Pour cela, vous vous tenez informés de l'évolution du monde du développement et de ses principes de fabrication. En lisant des blogs sur les bonnes pratiques de développement, par exemple.

A travers l'écriture de ce post, j'aimerais vous fournir quelques rapides techniques pour apprendre les principes de développement et vous permettre de les mettre en pratique ensuite. Pour ce faire, utilisons quelques saynètes.

Loi de Demeter

La semaine dernière, j'étais tranquillement assis au volant de ma voiture quand je me suis arrêté à la station service pour me dégourdir les jambes et m'acheter quelque chose à boire. J’ai attrapé la canette dans le frigo, l'ai posée sur le comptoir et j'ai attendu que le caissier me dise

ça fera $1,95

A ce moment-là, le plus naturellement du monde, j'ai enlevé mon pantalon. Le mec a commencé à hurler à l'indécence et a menacé d'appeler la police. Alors, confus, j'ai expliqué

Je suis justement en train d'essayer de vous payer! Regardez : je vous tends mon pantalon, vous n'avez qu'à fouiller mes poches, trouver mon portefeuille, le sortir et prendre l'argent nécessaire. S'il y a trop, remettez la monnaie dans le portefeuille, puis vous le remettez dans la poche du pantalon et enfin vous me rendez le pantalon.

Là, le type a sorti un fusil de derrière le comptoir en me disant que dans sa boutique, soit on obéit à la loi de Demeter soit on s'en va.

illustration

Mais que dit la loi de Demeter ? Entre autre, elle dit ceci

donnez à vos collaborateurs exactement ce qu'ils demandent et ne leur fournissez pas quelque chose qu'ils vont devoir inspecter à la recherche de ce dont ils ont besoin.

C'est la raison pour laquelle on ne tend pas son pantalon au caissier (ou même son portefeuille) mais qu'on lui donne directement l'argent. Il n'est pas pertinent de lui faire fouiller vos affaires à la recherche d'argent. La loi de Demeter vous encourage à faire de même pour votre code. Ne présentez pas votre pantalon au client de votre méthode en le forçant à chercher ce qui l'intéresse en invoquant Pants.Pockets[1].Wallet.Money, donnez-lui directement Money. Et si vous êtes le caissier, n'acceptez pas qu'on vous tende un pantalon pour y chercher vous-même la monnaie, demandez l'argent ou sortez votre fusil.

Single Responsibility Principle - SRP

Il y a peu de temps, ma femme et moi avons investi dans l'immobilier. Il s'agit d'une petite maison construite dans les années 50. Bien qu'agréable et charmante, elle ne dispose pas de toutes les commodités modernes dont j'aimerais profiter. J'ai donc dû me lancer dans quelques petits chantiers, refaire les sols, construire quelques trucs et en détruire d'autres. Ce genre de bricoles.

J'ai ainsi décidé d'installer un système d'élimination des déchets, celui-ci se composant de deux parties : la partie plomberie et la partie électricité. La partie plomberie s'est révélée relativement simple : il a suffi d'enlever le tuyau de vidange existant et d'insérer le nouveau dispositif entre la vidange et le tuyau. La partie électricité a été plus fastidieuse puisqu'il a fallu installer un interrupteur permettant d'allumer ou d'éteindre ce dispositif. Naturellement, je n'ai pas eu envie de créer de toute pièce un nouveau circuit allant du compteur général jusqu'à ma cuisine, juste pour cet interrupteur. J'ai donc décidé d'en utiliser un qui était déjà là : l'interrupteur de l'entrée, celui-ci servant justement à allumer ou éteindre la lumière de cette même entrée. Mais je lui ajouté une autre fonction : celle de contrôler mon broyeur.

Et c'est vrai que ça fonctionne super bien. Jusqu'à aujourd'hui, nous n'avons eu qu'un seul petit problème, un jour où nous recevions des amis à dîner. Je vidais les épluchures du repas que nous venions de préparer lorsqu'ils ont sonné. Ma femme s'est dépêchée de les accueillir et a allumé la lumière de l'entrée au moment précis où je faisais tomber une cuillère dans le dispositif. Heureusement, je n'ai eu à déplorer qu'une éraflure mineure et la perte d'une cuillère. Au final, ce n'est pas grand-chose à payer en comparaison de l'installation d'un tout nouveau circuit électrique. Mais pensez-vous réellement, qu'il s'agisse du pire qu'il puisse m'arriver ?

Nous sommes d'accord que la pire chose qui puisse arriver serait que l'un d'entre nous perde la main à cause de cette conception absurde. Parce que vous êtes allés à l'encontre du SRP, que l'on pourrait décrire grossièrement comme

faire une seule chose mais la faire bien

ou bien

n'avoir qu'une seule raison de changer

Or ici, nous avons 2 raisons d'utiliser l'interrupteur : allumer le broyeur ou éclairer l'entrée. Cela crée un problème évident! Et le parallèle avec le code est vrai. Si vous avez une classe qui doit être changée, lorsque le schéma évolue ou encore que la GUI évolue, alors vous avez une classe qui sert 2 maîtres. Dans ce cas, il est possible que les évolutions induites par une contrainte affectent l'autre responsabilité. Comme l'espace disque n'est pas très cher et que les classes/namespaces/modules sont des ressources renouvelables, dans le doute, n'hésitez pas, séparez!

Open/Closed Principle - OCP

Je n'ai pas vraiment eu le temps de regarder la télé ces derniers jours. Pour une bonne et simple raison : ma télé est terriblement chronophage. Tout allait bien à l'époque où la télé ne diffusait qu'un simple signal analogique qu'on captait depuis la prise murale. Mais le temps du tout-numérique est arrivé. J'ai donc du trafiquer ma télé et la recâbler pour qu'elle traite le signal. Le pire, ça a été quand je me suis fâché avec mon fournisseur d'accès et que nous sommes passés chez DISH (fournisseur de télé par câble américain), j'ai dû rebricoler encore une fois ma télé. Maintenant, nous avons une Nintendo Wii, un lecteur de DVD et un Roku (lecteur de streaming). Entre nous, j'aimerais vraiment savoir qui a le temps de bidouiller sa télé et de la recâbler pour qu'elle sache traiter chacun de ces nouveaux terminaux... Mais comme j'aime le challenge, j'ai essayé l'an dernier de brancher une vieille Sega MasterSystem. Mon terminal Dish n'a pas supporté, il a cessé de fonctionner.

…Sachez-le, jamais personne n'a fait ça. Pour la bonne et simple raison que personne ne vous explique jamais que votre télé a été conçue en respectant l'OCP. Ce principe signifie que vous devez fabriquer des composants qui sont fermés à la modification mais ouverts à l'extension. En d'autres termes, les télévisions ne sont pas prévues pour que vous les bricoliez vous-même. J'imagine aussi que personne ne conçoit devoir trifouiller les entrailles de sa télé juste pour brancher un composant. C'est d'ailleurs pour éviter cela que les câbles et connecteurs Coax/RCA/Component/HDMI/etc. ont été créés. Ces connecteurs et le fait que la télé soit scellée sous garantie induisent que votre télévision est ouverte à l'extension mais fermée à la modification. Vous pouvez étendre ses capacités en branchant le périphérique de votre choix, voire même en branchant des composants qui n'existent pas encore, comme par exemple une X-Box 12. C'est le même principe que je vous invite à suivre pour avoir du code flexible. Lorsque vous codez, efforcez vous de maximiser la flexibilité en facilitant la maintenance à l'aide d'extensions. Si vous programmez à l'aide d'interfaces ou que vous permettez de modifier le comportement à travers l'héritage, la vie sera beaucoup plus simple quand viendra l'heure du changement. Allez dans ce sens plutôt que d'écrire la bonne grosse classe qui fait tout et que vous allez devoir comprendre puis modifier à chaque sprint. C'est crado et vous allez finir par vraiment la détester, cette classe. Sa conception aussi. De la même manière que vous détesteriez la télévision décrite plus haut.

Liskov Substitution Principle - LSP

Le soir, j'aime dîner léger. J'ai donc pour habitude de manger une bonne salade bien classique. Vous savez, ce genre de salade avec des tomates, des croûtons, de l'oignon, des carottes et de la ciguë. Du coup, je mange ma salade d'une façon assez particulière : à chaque coup de fourchette, j'examine les éléments avant de les mettre dans ma bouche (beaucoup d'amateurs de salades composées vont beaucoup plus vite mais jouent avec leur vie selon moi). Pour ce faire, je déroule un algorithme simple. Si l'ingrédient que j'ai dans ma fourchette n'est pas de la ciguë, alors je le mange. Si c'est de la ciguë, je le repose dans un coin de mon assiette pour le jeter plus tard. C'est, à mon avis, la seule façon de manger votre salade de ciguë en toute sécurité.

Sinon, vous pouvez penser au LSP. Ce principe explique qu'en cas de relation d'héritage, chaque dérivé devrait pouvoir être remplacé par son type de base. Ainsi, dans ma salade de comestibles, je ne devrais pas avoir un type dérivé cigüe qui ne se comporte pas de la même façon que les autres comestibles, c'est à dire qu'il m'empoisonne. Autre cas, imaginons une collection d'objets hétérogènes dans une hiérarchie d'héritage. Dans ce cas-là, vous n'irez pas parcourir les éléments les uns après les autres et vous demander

voyons de quel type est celui-ci puis adaptons le traitement

Alors respectez le LSP et ne préparez jamais une salade à la ciguë. Pour personne. De cette façon, vous aurez un code plus propre et vous éviterez la prison.

Interface Segregation Principle - ISP

Dieu soit loué le caching de pages web !!! Ça peut sauver des vies !!! Quand je consulte mon dictionnaire en ligne fétiche, expertbeginnerdictionary.com (ceci n'est pas un vrai site, pour ceux qui pensaient le visiter), on me demande un mot à rechercher. Dès que je le saisis, on m'envoie le dictionnaire à travers le réseau, puis avec Ctrl-F, je peux rechercher ma définition. Le problème, c'est que le navigateur met un temps infini à charger le dictionnaire en entier. Ça serait vraiment pénible sans ce mécanisme de cache. Le problème, c'est quand un mot du dictionnaire change. Dans ce cas-là, le cache est invalidé et ma prochaine recherche devra recharger le dictionnaire complet. Si seulement il y avait une meilleure façon de faire...

Mais en fait si, il y en a une qui ne renvoie pas tout le dictionnaire dès que je cherche un mot. Elle renvoie juste la définition de ce mot. Si je veux savoir ce qu'est un zèbre, je me moque de connaître la signification de aardvark. En plus, ma recherche de zèbre ne sera pas affectée par un quelconque changement sur le mot aaedvark. En principe, ma recherche ne devrait être dépendante que des mots et définitions que j'utilise au même moment, mais jamais de tout le dictionnaire. De la même manière, si vous définissez des interfaces publiques à destination de vos clients, essayez de les découper en parties les plus petites possibles et composables entre elles. Et laissez à votre client le soin de les assembler comme bon lui semble, plutôt que de le forcer à composer avec le tout (ou le dictionnaire). L'ISP précise qu'il ne faut pas forcer les clients d'une interface à dépendre d'une méthode dont ils n'ont pas besoin. Donnez à votre client le minimum syndical.

Dependency Inversion Principle - DIP

Avez-vous déjà visité une usine automobile ? C'est fou de voir la façon dont est assemblée une voiture. On part d'un chassis puis la voiture assemble son moteur, ses sièges, ses roues, etc. C'est vraiment incroyable à observer. Si vous voulez vous régaler, regardez ces sous-parties assembler elles-même leurs composants internes. Par exemple, le moteur construit son alternateur, sa batterie, sa transmission. C'est un coup de génie de l'industrie. Bien sûr, il y a un inconvénient à tout. Et aussi cool que cela puisse paraître, ça doit-être quand même un peu frustrant pour les ouvriers de n'avoir aucun contrôle sur ce moteur que la voiture s'est construit elle-même. Tout ce qu'ils peuvent faire, c'est dire

je veux une voiture

et la voiture fait le reste.

Je parie que vous imaginez la base de code que je décris. Il y a longtemps, j'ai filé cette métaphore dans l'article ici. Ici je vais résumer en disant qu'il s'agit du pattern "commande et contrôle" où les constructeurs d'objets instancient tous les objets dont ils dépendent. Ainsi le FooService instancie son propre logger. Cette façon de faire va à l'encontre du DIP. Le DIP préconise que les modules de haut niveau, comme Voiture par exemple, ne soient pas dépendants des modules de bas-niveau, ici le Moteur. Ils devraient plutôt dépendre d'une abstraction, ici ça serait l’interaction Voiture-Moteur. Cela permet de modifier indépendamment le moteur ou la voiture. Ça signifie que nos mécaniciens peuvent décider de quel moteur va dans quelle voiture. Comme décrit dans le lien, une base de code construite en respectant ce principe tend à être composable. Alors que celle basée sur le pattern "Commande et Contrôle", favorisant l'approche "voiture, construis-toi toi-même", ne le sera pas. Pour bien intégrer le DIP, demandez-vous qui doit décider des morceaux à embarquer dans la construction de la voiture. Les gens qui construisent la voiture ou alors la voiture elle-même ? Attention, une de ces deux idées est absurde.

…Cinq des principes décrits ici composent les principes SOLID. Pour en savoir plus sur le sujet, je vous invite à consulter les cours Pluralsight sur le sujet.

Plus de publications

6 comments for “Les principes SOLID dans la vie de tous les jours

  1. max
    22 février 2017 at 11 h 25 min

    Génial comme article 😉

    Coquille dans la première ligne de ISP “Quand que je consulte”

  2. Raphaël Squelbut
    22 février 2017 at 15 h 23 min

    Vous imaginez bien que c’est exprès !!!
    Plus sérieusement, merci pour la vigilance 🙂 et pour l’appréciation

    (ce n’est pas faute de l’avoir relu pourtant)

  3. ipsus
    22 février 2017 at 18 h 29 min

    “Si vous avez une classe qui doit être changée, lorsque le schéma évolue ou encore que la GUI évolue, alors vous avez une classe qui sert 2 maîtres”

    Quel est votre avis sur utilisation de pojo/entité JPA comme bean de présentation (côté client dans le cadre d’une application web) est ce une violation du srp?

  4. Raphaël Squelbut
    25 février 2017 at 15 h 29 min

    Effectivement un POJO servant à la fois d’entité JPA et de bean de représentation est un exemple typique de violation du SRP.
    Sauf si le but de votre IHM est de représenter tels quels le contenu de vos données brutes.

  5. Adirelle
    31 mai 2017 at 23 h 21 min

    Intéressant mais il y a erreur (dans l’article original) concernant le DIP : il semble que l’auteur ait confondu “Dependency Inversion” et “Dependency Injection”. Cf. wikipedia : https://fr.wikipedia.org/wiki/SOLID_(informatique)

  6. 8 juillet 2021 at 11 h 33 min

    super article de vulgarisation, merci!