Le Blog technique d'Adimeo

Comment devenir un Dev SOLID ?

Rédigé par Agnès Guillet | Nov 24, 2022 7:17:00 AM

L’acronyme SOLID a été inventé par Michael Feathers, mais c’est Robert C. Martin (architecture logicielle propre, clean code) qui a popularisé ce principe.

Dans la programmation orienté objet, le concept SOLID repose sur 5 principes :

  • S : Single Responsibility Principle ou principe de la responsabilité unique ;
  • O : Open / Closed Principle ou principe Ouvert / Fermé ;
  • L : Liskov’s Substitution Principle (LSP) ou principe de substitution de Liskov ;
  • I : Interface Segregation Principle (ISP) ou principe de ségrégation des interfaces ;
  • D : Dependency Inversion Principle ou principe de dépendance inversée.

Maintenant que vous connaissez la signification de cet acronyme, je vais m’attarder un peu plus sur chacun de ces principes.

 

Le principe de la responsabilité unique

Regroupe les choses qui changent pour la même raison. Sépare les choses qui changent pour des raisons différentes.
Robert C. Martin

Le principe de la responsabilité unique est sans doute le principe le plus simple à comprendre, une classe ne doit avoir qu’une seule et unique responsabilité.

Très bien, mais pourquoi ? Dans les bonnes pratiques informatiques, il y a une phrase légendaire qui dit : « Fais une seule chose, mais fais-la bien ».

Chaque responsabilité est un accès au changement. Lorsqu'une classe a plus d'une seule responsabilité, ces responsabilités deviennent couplées. Ce couplage peut conduire à une base de code fragile ainsi qu'à une difficulté de re-factorisation.

Attention, cependant à l’effet inverse en poussant le concept trop loin et en créant des classes à tout va.

> Les avantages du principe de responsabilité unique

  • Le code est beaucoup plus clair (pas des classes de 1000 lignes) ;
  • Chaque classe a désormais un rôle qui lui est propre. On a une meilleure compréhension grâce au nommage qui est plus représentatif ;
  • Les tests unitaires seront plus simples à écrire ;
  • Le projet sera beaucoup plus facile et maintenable.
 

 

Le principe Ouvert / Fermé

Un module doit être ouvert à l’extension, mais fermé à la modification.
Robert C. Martin

Ce deuxième principe nous dit que les entités doivent être ouvertes à l'extension et fermées à la modification. On doit toujours favoriser l’extension du code à sa modification : on ne modifie pas le fonctionnement suivant l’entité à utiliser, on va définir une fonction commune.

Pour imager un peu, vous voyez les enfants qui essayent de mettre le carré dans le rond ? Eh bien, c’est pareil... on va choisir de créer une classe plus générique comme « FormeGéométrique », qu’on spécialisera ensuite en rond, triangle, etc.

Attention ! Si tu commences à utiliser des « instanceof » avec des « if » et des « switch case » en fonction d’un type, c’est sans doute que vous ne respectez pas ce principe.

> Les avantages du principe Ouvert / Fermé :

  • La réduction du risque de casser des fonctionnalités existantes ;
  • On a moins de bugs et/ou de comportements bizarres ;
  • Une meilleure maintenabilité.

 

Le principe de substitution de Liskov (LSP)

On va parler de Barbara Liskov et de son principe de son principe de substitution. En 1988 Barbara Liskov avait décrit les sous type ainsi :

Si pour chaque objet o1 du type S, il existe un objet o2 du type T tel que tous les programmes P qui sont définis en termes de T permettent de ne pas modifier le comportement de P lorsque o1 est remplacé par o2 il en découle que S est un sous type de T.

Je vous l’accorde, c’est un peu théorique ! Mais ce qu’il faut comprendre, c’est que les classes qui héritent d'une même classe parente, partagent aussi un état et/ou un comportement, elles doivent s'y conformer.

Si ce comportement n'est pas désiré dans la classe enfant, alors le principe de LSP n'est pas respecté. On va se retrouver à gérer des cas particuliers, avec une architecture un peu « polluée ».

Pour être conforme au LSP, on fait attention à :

  • La signature des fonctions doit être identique entre l’enfant/parent ;
  • Les paramètres de la fonction de l’enfant ne peuvent pas être plus nombreux que ceux du parent ;
  • Le retour de la fonction doit retourner le même type que le parent ;
  • Les exceptions retournées doivent être identiques.

> Les avantages du principe LSP

  • On évite les bugs sur lesquels une classe enfant qui fait davantage qu’un parent (plus de paramètres, de retours, d’exceptions levées, etc.) ;
  • Les mises à jour de code seront plus faciles à organiser (chaque changement du parent induit des changements vers les enfants = sécurité) ;
  • On gagne au niveau lisibilité et qualité du code.

Pour en savoir plus sur Barbara Liskov, c’est par ici.

 

Le principe de ségrégation d’interface

Créer des interfaces courtes pour que les utilisateurs n’aient pas à dépendre de choses dont ils n’ont pas besoin .
Robert C. Martin

Une classe ne doit pas dépendre d’interfaces dont elle n’a pas l’utilité. L’objectif est de réduire le couplage inutile entre les classes.

Par exemple, on veut ajouter une méthode à une interface existante. Est-ce que toutes les classes qui implémentent cette interface ont besoin de cette nouvelle méthode ?

On évite d'avoir des fonctions dans nos classes dont on n'a pas besoin, il vaut mieux faire plusieurs petites interfaces qu’une seule grande. En incorporant des éléments fonctionnels dont on n'a pas besoin, on prend le risque de subir des bugs inattendus.

> Les avantages du principe de ségrégation d’interface

  • Améliorer la qualité du code ;
  • Le code est plus modulable (meilleure réutilisation) ;
  • Avoir des petites interfaces facilitera la lecture et la compréhension du code ;
  • On respecte aussi le principe de responsabilité unique.

 

Le principe d’inversion des dépendances

Les modules de haut-niveau et de bas-niveau doivent tous deux dépendre d’abstractions, et les abstractions ne doivent jamais dépendre de leurs implémentations concrètes.
Robert C. Martin

Une classe doit dépendre de son abstraction, pas de son implémentation. Ce principe nous dit que les classes de haut niveau ne sont pas censées changer à cause des modifications réalisées dans les classes de bas niveau.
Autrement dit, on évite de passer des objets en paramètre lorsqu’une interface est disponible. On fait rarement de l’injection de dépendance sans interface ou sans vérifier les types de variable que l’on va recevoir. On va privilégier plutôt l'interface plutôt que l'objet, ce qui permet d’être certain que l’objet qu'on manipule, peu importe son type, aura les bonnes méthodes associées.
L’intérêt de ce principe est de décentraliser la dépendance.

> Les avantages du principe d’inversion des dépendances

  • Un code plus facile à modifier ;
  • Une lisibilité et une meilleure qualité de code.

 

Après cette rapide présentation du concept SOLID. Nous pouvons nous questionner sur les bienfaits de ces principes. Ne sont-ils pas trop surestimés ? En effet, ils s'appliquent principalement au sous-typage (héritage, interface...) et c’est loin d’être le type de relation que j’utilise le plus.

Est-ce que le principe SOLID s'applique aussi dans un principe de composition plus souvent utilisée (instance de classe dans une autre) ? Même le principe de composition n’est pas épargné par les mauvaises pratiques. Qui n'a pas déjà eu un projet ou il est totalement impossible de modifier la moindre ligne de code sans tout casser ? 😱

Vous l’aurez compris, ces principes nous aident à organiser les briques pour construire les murs et délimiter les pièces de notre application, faire du code SOLID représente des avantages non négligeables, il est plus facile à lire, à tester, plus modulable et maintenable.

Cependant, on privilégie la réflexion à la croyance, on essaye d'adopter ces principes, mais pas à n'importe quel prix. 😊

 

Sources :

  1. Architecture logicielle propre de Robert C. Martin (livre)
  2. Apiumhub - article de juin 2017
  3. So yes. - article de décembre 2021
  4. Digital Ocean - article de mars 2021

Crédit photo : scyther5