Domaine Sciences et Technologies
Master Bio-informatique : DLAD
Programmation avancée : SOLID
Code UE : SBICU05
Année 2023-24 Principes SOLID
Introduction
Cinq principes pour un code maintenable
En programmation orientée objet, il existe cinq principes de conception destinés (regroupés sous l’acronyme
SOLID) qui visent à produire des architectures logicielles plus compréhensibles, flexibles et maintenables. Ces
principes sont un sous-ensemble de nombreux principes promus par l’ingénieur logiciel et instructeur américain
Robert Cecil Martin (familièrement connu sous le nom Uncle Bob). Bien qu’ils s’appliquent à toute conception
orientée objet, les principes SOLID peuvent également former une philosophie de base pour des méthodologies
telles que le développement agile. La théorie des principes SOLID a été introduite par Martin dans son article
Design Principles and Design Patterns de 2000, bien que l’acronyme SOLID ait été introduit plus tard par
Michael Feathers.
Les cinq principes SOLID sont les suivants :
— Single Responsibility Principle (SRP) : Une classe ne doit avoir qu’une seule responsabilité
— Open/Closed Principle (OCP) : Programme ouvert pour l’extension, fermé à la modification
— Liskov Substitution Principle (LSP) : Les sous-types doivent être substituables par leurs types de
base
— Interface Segregation Principle (ISP) : Éviter les interfaces qui contiennent beaucoup de méthodes
— Dependency Inversion Principle (DIP) :
— Les modules d’un programme doivent être indépendants
— Les modules doivent dépendre d’abstractions
Le but de ces principes est donc de garantir la maintenabilité d’un programme, c’est-à-dire sa capacité à :
— absorber les changements avec un minimum d’effort ;
— implémenter les nouvelles fonctionnalités sans toucher aux anciennes ;
— modifier les fonctionnalités existantes en modifiant localement le code.
L’application des principes SOLID a pour objectifs :
— de limiter les modules impactés ;
— de simplifier les tests ;
— de rester conforme aux spécifications qui n’ont pas changé.
Approche qualité des 5S
Les principes SOLID sont donc ceux qui seront étudiés dans ce cours et constitueront son ossature. Dans la suite
de ce cours, nous allons donc détailler ces cinq principes et expliquer pourquoi il est important de les respecter
afin d’obtenir du code maintenable. Mais avant cela, il est utile de nous intéresser à une approche qualité pour
la gestion de projet, venue du Japon au milieu du siècle dernier et qui peut s’appliquer à l’informatique. Cette
approche ou philosophie des 5S peut être résumé par les cinq points suivants :
— Seiri (“s’organiser”) : les différents éléments d’un code doivent être structurés et aisément identifiables.
Ainsi une action aussi anodine que de nommer les identifiants, méthodes et classes ne l’est pas tant que
cela et doit requérir toute votre attention ;
— Seiton (“situer”) : un morceau de code doit se trouver là où l’on s’attend logiquement à ce qu’il se trouve.
Si ce n’est pas le cas, cela veut dire qu’il n’est pas à sa place et que la structure du code n’a pas été
pensée correctement ;
— Seiso (“scintiller”) : l’espace de travail doit être propre ! Pensez à la cuisine d’un grand restaurant : on
ne travaille pas sur un plan de travail comportant de la vaisselle sale, des ingrédients d’un autre plat, . . .
Au niveau du code la présence de commentaires non informatifs ou de code ancien désactivé par une
mise en commentaire constitue une pollution de l’espace de travail à laquelle il faut remédier ;
— Seiketsu (“standardiser”) : dans un travail en équipe il faut que des conventions soient respectées, que
chaque développeur suive les mêmes règles pour que le code conserve une homogénéité ;
— Shutsuke (“suivi”) : suivre le travail des autres permet de s’interroger sur ses propres pratiques et
d’évoluer positivement (en tout cas il faut l’espérer. . .).
On peut se poser la question de pourquoi s’imposer ces règles qui viennent s’ajouter à celles qu’il faut déjà
suivre pour qu’un programme soit fonctionnel. En fait, le but de ces règles est de gagner du temps. Le temps
de travail des personnes participant au projet est généralement la ressource qui est la plus coûteuse dans un
projet. Cela peut paraître contre-intuitif, car appliquer les règles énoncées ci-dessus prend clairement du temps.
Cependant, ce temps n’est pas perdu, car l’objectif est d’en gagner dans le futur. En effet, l’objectif est de
garder un bon cadre de travail afin d’être efficace. C’est un peu le même principe qu’avoir une pièce bien rangée
vous fait globalement gagner du temps, car cela vous permet d’accéder facilement à vos affaires, et ce même si
ranger prend du temps.
Comme vous venez de le voir, en termes de bonnes pratiques on parle souvent de règles pour évoquer les principes
recommandés par une approche ou l’autre. Il est important de comprendre qu’il s’agit bien de recommandations
et non de lois immuables. Tout ce que nous allons voir dans la suite est à adapter au contexte dans lequel vous
allez l’appliquer, il n’y a pas de loi universelle permettant d’obtenir à coup sûr un code propre et maintenable,
seulement des indications de pratiques qui ont été reconnues profitables. De surcroît, il se peut que certaines
considérations, notamment des contraintes de performances, puissent rendre difficile l’application de certaines
bonnes pratiques de programmation dans des cas très spécifiques.
La vie d’un programme
On peut se demander pourquoi il est si important d’avoir une certaine méthodologie (et donc de se fixer des
règles et d’appliquer des bonnes pratiques) lorsqu’on participe à un projet de développement logiciel d’envergure.
Pour justifier cette approche, nous allons considérer l’historique de la vie d’un programme quelconque. On peut
faire une analogie avec la vie humaine et découper la vie d’un projet en 5 phases :
— La naissance : tout le monde vient s’extasier sur le beau bébé qui vient de naître. Les parents sont fiers
2
de présenter leur rejeton. Le code est beau, pur, les développeurs ont porté une grande attention à sa
création.
— L’enfance : en commençant à marcher, courir, sauter, etc., l’enfant se blesse. Pour réparer un premier bug
ou ajouter rapidement une nouvelle fonctionnalité les développeurs travaillent à la va-vite, ils ajoutent
une rustine (c’est-à-dire une modification du code sommaire et temporaire visant à corriger rapidement
un bug ou dysfonctionnement) au projet. Le code devient donc moins pur et moins beau.
— L’adolescence : le moment de la rébellion. Il faut intervenir de plus en plus souvent, car les bugs se
multiplient. Les développeurs multiplient les rustines et le code devient de moins en moins maintenable.
— L’âge adulte : il faut avancer coûte que coûte. Les modifications de code précédentes sont un lourd
héritage et toute amélioration ou correction prend énormément de temps. Parfois la correction d’un bug
déclenche l’apparition de nombreux autres bugs. Mais il faut continuer à avancer : le programme est en
production, il n’y a pas d’autre choix que de perdre un temps précieux dès qu’il faut modifier le code.
— La vieillesse : certaines fonctionnalités sont défaillantes, mais on ne peut plus les réparer. À force
d’ajouter des rustines le code n’est plus maintenable, plus aucun développeur ne peut effectuer la moindre
modification sans tout casser. Il n’y a plus rien à faire qu’attendre une mort inexorable.
Respecter les bonnes pratiques de conception et de développement permet à un code de vieillir sereinement, de
conserver ces fonctionnalités le plus longtemps possible. C’est comme pour un être humain : mener une vie
d’excès ne permet pas d’envisager une longue vie en ayant la jouissance complète de ces capacités intellectuelles
et physiques. Avec le code informatique, c’est encore plus important, car on développe rarement seul. Une
mauvaise hygiène de vie aura des répercussions sur les développeurs faisant également partis du projet et fera
nécessairement naître des tensions.
Le zen du développement
En Python, l’un des documents qui définit le langage est une ôde aux bonnes pratiques. Il s’agit du PEP 20
(Python Enhancement Proposal), intitulé le Zen of Python :
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
3
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
En français cela donne :
Le beau est préférable au laid.
L’explicite est préférable à l’implicite.
Le simple est préférable au complexe.
Le complexe est préférable au compliqué.
L’horizontal est préférable à l’imbriqué.
L’aéré est préférable au dense.
La lisibilité compte.
Les cas spéciaux ne le sont pas assez pour transgresser les règles.
Sauf si le cas pratique bat le cas théorique.
Les erreurs ne devraient jamais arriver silencieusement.
Sauf si on les a explicitement rendues silencieuses.
En cas de doute, ne tentez pas de deviner.
Il devrait y avoir une, et de préférence une seule, manière évidente de le faire.
Même si cette manière peut ne pas sembler évidente au premier abord sauf si vous êtes néerlandais.
Même si jamais est souvent mieux que tout de suite.
Si l’implémentation est difficile à expliquer, c’est que c’est une mauvaise idée.
Si l’implémentation est facile à expliquer, c’est que c’est peut-être une bonne idée.
Les espaces de noms sont une brillante idée, créons-en plus !
Dans cette introduction, nous avons pu observer qu’il n’y avait pas que les principes SOLID qui existent en
termes de bonnes pratiques de programmation. D’ailleurs nous avions même évoqué d’autres bonnes pratiques
dans le premier cours portant sur la gestion de version et les tests. Toutes ces recommandations ont vu le jour à
peu près à la même période. Comme on vient de l’expliquer elles ont toutes le même objectif : coder proprement
de manière à conserver un code maintenable le plus longtemps possible. Finalement, à y regarder un peu plus
en détail, que ce soit la philosophie des 5S, le Zen de Python ou les principes SOLID, tous reprennent plus ou
moins les mêmes idées. . . il y a donc sans doute un enseignement intéressant à en tirer !
Principe de responsabilité unique
Le “S” de SOLID signifie Single Responsability Principle, également généralement noté SRP. Robert Cecil
Martin dans son livre “Agile Software Development, Principles, Patterns, and Practices” définit ce principe de
la manière suivante :
Single Responsability Principle : A class should have only one reason to change.
4
En français, cela donne :
Principe de responsabilité unique : une classe ne doit avoir qu’une seule raison de changer.
Les classes (et les méthodes) ne devraient avoir qu’une seule fonctionnalité.
Considérons une implémentation d’un jeu dans lequel il a des tours de jeu et un comptage de points comme le
bowling. On pourrait imaginer qu’un tel jeu aura une classe Game qui aura la responsabilité de se souvenir du
numéro du tour en cours (à quel carreau on est dans le cas du bowling) ainsi que du calcul du score (quel est
le nombre de points obtenu par chacun des joueurs). Pour respecter le principe SRP, il faudrait séparer ces
deux fonctionnalités en deux classes de sorte que chaque classe n’ait qu’une seule responsabilité. La classe Game
garderait la responsabilité du suivi des tours alors qu’une nouvelle classe Scorer aurait la responsabilité de
calculer le score.
On peut se poser la question de savoir pourquoi il est important de séparer ces deux responsabilités dans des
classes distinctes. La raison en est que chaque responsabilité correspond à une direction dans lequel on peut
faire un changement dans la classe. Si une classe possède plus d’une responsabilité, elle aura donc plus d’une
raison de changer. Si une classe a plusieurs responsabilités, elles sont couplées. Dans ce cas, la modification
d’une des responsabilités nécessite de :
— tester à nouveau l’implémentation des autres responsabilités ;
— modifier potentiellement les autres responsabilités (les modifications apportées à une responsabilité
pouvant compromettre la capacité de la classe à assurer ses autres responsabilités) ;
— déployer à nouveau les autres responsabilités.
Ce type de couplage conduit à des conceptions fragiles qui se brisent de manière inattendue lorsqu’elles ont
besoin d’être modifiées. Une modification dans les spécifications d’une des responsabilités d’une classe peut
entraîner l’introduction de bugs et donc une perte de temps.
Séparer les responsabilités et donc respecter SRP a de nombreux avantages :
— Diminution de la complexité du code
— Amélioration de la lisibilité du code
— Meilleure organisation du code
— Modification locale lors des évolutions
— Augmentation de la fiabilité
— Classes davantage réutilisables
Afin d’illustrer ce principe, nous allons décrire un exemple plus concret. On va considérer une classe Rectangle
qui a deux méthodes : une méthode draw() permettant de dessiner le rectangle et une méthode area()
calculant l’aire de celui-ci. Deux classes différentes utilisent la classe Rectangle : GeometricApplication
et GraphicalApplication. La classe GeometricApplication utilise Rectangle pour faire des calculs ma-
thématiques sur des formes géométriques (pour faire simple des calculs d’aires), mais ne dessine jamais de
rectangle à l’écran. La classe GraphicalApplication est de nature graphique et peut également faire de la