Le code est rarement écrit pour rester figé. Les exigences changent, les fonctionnalités s’ajoutent, et la base de code grandit. C’est à ce moment que les projets mal structurés deviennent rigides, fragiles et coûtent cher à maintenir, le tout se cristallisant en dette technique.
Pour lutter contre cette fatalité, le développeur avisé se tourne vers les principes S.O.L.I.D., formalisés par Robert C. Martin (“Uncle Bob”) au début des années 2000. Loin d’être de simples règles académiques de la Programmation Orientée Objet (POO), S.O.L.I.D. est un ensemble de guides stratégiques pour la conception de systèmes qui tolèrent le changement.
On n’active pas tous les principes à fond sur un CRUD jetable, mais dès qu’on sait que le logiciel va vivre, on n’a plus le luxe d’ignorer ces règles.
Le Postulat : Maintenabilité vs. Simplicité
L’effort initial pour appliquer S.O.L.I.D. est un investissement. Il permet de :
- Réduire le Couplage : Diminuer les dépendances entre les modules.
- Augmenter la Cohésion : S’assurer que chaque module a une raison d’être unique et bien définie.
- Faciliter l’Extensibilité : Permettre l’ajout de nouvelles fonctionnalités sans toucher au code stable existant.
On peut livrer vite une fois, mais on ne peut pas livrer vite longtemps sans structure.
S.O.L.I.D. rend les tests unitaires possibles sans gymnastique.
S : Single Responsibility Principle (SRP)
Principe : Une classe ne doit avoir qu’une seule raison de changer.
Le SRP est souvent mal interprété. Il ne signifie pas qu’une classe ne peut avoir qu’une seule méthode, mais qu’elle ne doit avoir qu’un seul acteur (ou rôle) ou une seule responsabilité métier qui pourrait dicter une modification.
Conséquence Pratique : Le Coût du “Couteau Suisse”
La violation du SRP conduit aux classes “Dieu” (God Objects) :
Exemple de Violation
Une classe UserService qui gère l’authentification, la journalisation, l’envoi d’e-mails de bienvenue, et la persistance des données utilisateur. Si l’on change le format de l’e-mail, on modifie la classe, risquant de casser la persistance.
Le Vrai Danger
Chaque raison de changer devient un vecteur de bugs potentiels pour le reste de la classe. Le test unitaire devient lourd.
Solution : L’Isolation par la Composition
La solution est la Composition et la délégation des tâches. Isoler la responsabilité dans des services dédiés :
UserAuthenticatorgère l’authentification.UserNotifiergère l’envoi d’e-mails.UserRepositorygère la persistance.
Votre service principal (UserService) utilise ces composants via l’Injection de Dépendances, respectant ainsi le SRP.
O : Open/Closed Principle (OCP)
Principe : Une entité logicielle (classe, module, fonction) doit être ouverte à l’extension, mais fermée à la modification
L’OCP est au cœur d’une architecture extensible. Il stipule que pour ajouter une nouvelle fonctionnalité, vous devriez écrire de nouveau code, et non modifier le code existant qui a été testé et validé.
Conséquence Pratique : L’Architecture “Plug-and-Play”
Comment être “fermé à la modification” ? En dépendant des abstractions plutôt que des implémentations concrètes.
Exemple Concret
La gestion des rapports. Vous avez un service ReportGenerator. Si vous devez ajouter un nouveau format (JSON à côté de PDF) :
Violation OCP
Modifier ReportGenerator avec un nouveau if/else ou switch pour gérer le type JSON.
Solution OCP
Définir une interface ReportExporter. PdfExporter et JsonExporter implémentent cette interface. Le ReportGenerator dépend uniquement de l’interface (ReportExporter), et non des classes concrètes.
L’ajout du format XML se fait en créant une nouvelle classe XmlExporter sans toucher au code stable de ReportGenerator.
Notez bien que OCP sans conteneur d’injection ou sans point d’extension identifié, c’est de la théorie. Pour que ça fonctionne, il faut prévoir les points d’extension.
L : Liskov Substitution Principle (LSP)
Principe : Les objets d’une classe dérivée doivent pouvoir remplacer un objet de la classe de base sans altérer la cohérence du programme.
Le LSP est le gardien de l’héritage et du polymorphisme. Il s’assure que la substitution d’une classe mère par une classe fille ne crée pas de surprise pour le code client (l’appelant).
Conséquence Pratique : Le Contrat de Comportement
Le problème n’est pas la structure, mais le comportement attendu.
Le Piège (Le Carré vs. Le Rectangle)
Un Rectangle permet de modifier la largeur et la hauteur indépendamment. Si une classe Carré hérite de Rectangle, elle viole le LSP, car un Carré doit imposer l’égalité entre hauteur et largeur, ce qui pourrait causer des erreurs inattendues dans le code qui traite l’objet comme un Rectangle générique.
Règle LSP
Une sous-classe ne doit jamais lever de nouvelles exceptions non documentées, ne pas affaiblir les préconditions ou ne pas renforcer les postconditions de la méthode de la classe mère/interface.
En clair
Si vous utilisez une abstraction (interface ou classe de base), le client doit pouvoir lui injecter n’importe laquelle de ses implémentations sans que le programme ne change de manière incohérente.
Si votre implémentation commence à throw là où l’interface ne le fait pas, alors vous avez cassé LSP.
I : Interface Segregation Principle (ISP)
Principe : Les clients ne devraient pas être forcés de dépendre d’interfaces qu’ils n’utilisent pas.
Ce principe est la réponse directe aux “interfaces monstrueuses” et vise à réduire le couplage.
Conséquence Pratique : Contre l’Interface Monolithe
Violation ISP
Avoir une seule interface IDatabaseOperator qui contient create, read, update, delete, connect, logQuery, backup. Toute classe qui a besoin de lire doit implémenter toutes les méthodes, même si elle n’a pas besoin de faire de backup.
Solution ISP
Diviser en petites interfaces spécifiques : IReadableRepository, IWriteableRepository, IBackupService.
L’ISP garantit que lorsqu’une interface est modifiée, seul un ensemble minimal de classes est affecté, augmentant ainsi la flexibilité.
Si chaque changement d’interface vous déclenche 12 erreurs de compilation, vous avez violé ISP !
D : Dependency Inversion Principle (DIP)
Principe : Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d’abstractions.
C’est le principe qui rend possible l’Inversion de Contrôle (IoC) et l’Injection de Dépendances (DI), une pierre angulaire des frameworks comme Symfony et des architectures propres.
Conséquence Pratique : Le Découplage par l’Abstraction
Violation DIP (Couplage Fort)
Votre classe métier de haut niveau (OrderService) crée elle-même l’objet de bas niveau (new MySQLConnector()). Si vous changez la base de données, vous devez modifier le OrderService.
Solution DIP
Le OrderService dépend d’une interface (IDatabaseConnection). Le Conteneur d’Injection de Dépendances est responsable de fournir l’implémentation concrète (le MySQLConnector ou le PostgresConnector) lors de l’instanciation.
Le DIP inverse le sens de la dépendance. Au lieu que le module de haut niveau dépende du module de bas niveau, les deux dépendent d’une abstraction partagée, rendant votre code : testable, modulaire et agnostique à l’infrastructure.
Notez que DIP n’impose pas l’usage d’un framework, juste d’écrire contre des abstractions.
Conclusion : L’Héritage d’Uncle Bob
Les principes S.O.L.I.D. ne sont pas des dogmes rigides, mais un ensemble d’outils éprouvés pour gérer la complexité.
Ils sont complémentaires aux règles de conception tactiques (DRY, KISS), qui visent la simplicité immédiate.
Ils sont les gardiens de l’Architecture Anti-Fragilité : un système qui non seulement résiste aux changements, mais devient plus facile à modifier à chaque itération.
Si vous ne structurez pas,vous ne pouvez pas déléguer. Si vous ne pouvez pas déléguer, votre projet est condamné à rester petit !

