Commentaires
Votre code est STUPID ? Rendez le SOLID !
En dépit de son titre provocateur, cet article s'intéresse à deux grands
principes de la programmation orientée objet : STUPID et
SOLID. Ces deux acronymes représentent respectivement une série de
mauvaises et de bonnes pratiques de développement orientée objet.
Savoir identifier rapidement et formellement les indices qui montrent qu'un
code est « STUPID », c'est aussi comprendre les conséquences
nuisibles à la qualité générale de ce dernier. En revanche, savoir appliquer
les principes de SOLID à son code, c'est faire un premier pas vers
un code robuste et résistant aux changements.
Le principe STUPID
STUPID est l'acronyme pour Singleton,
Tight Coupling, Untestability,
Premature Optimizations, Indescriptive Naming et
Duplication. En français, ces six expressions se traduisent
respectivement par instances uniques, couplage fort,
non testabilité, optimisations prématurées,
nommage indéchiffrable et duplications.
C'est sans aucun doute que le lecteur aura compris que les six principes de
STUPID portent un jugement de valeur négatif sur le code. Qu'on se
rassure, personne n'est parfait et n'importe quel développeur écrit du code
« STUPID » à un moment ou à un autre. Après tout, chaque
développeur cherche avant tout à faire fonctionner ses applications... et
parfois à n'importe quel prix ! Quitte à en dégrader la qualité du code.
Cependant, il est quand même préférable de savoir reconnaître facilement ces
mauvaises pratiques pour mieux les corriger et les éviter dans les
prochaines applications. La qualité du code n'en sera qu'améliorée.
Instance unique
Les « singletons », ou les instances uniques de classe, sont très
largement appréciés et utilisés des développeurs. Ils sont devenus
populaires grâce notamment aux membres du GoF (Gang of Four) qui
l'ont formalisé en tant que patron de conception (design pattern en
anglais).
Au début des années 2000, les frameworks PHP tels que symfony et Zend
Framework utilisaient de nombreux objets uniques. On se souvient par exemple
de ces bons vieux sfContext
et
Zend_Controller_Front
accessibles par l'intermédiaire de leur
méthode statique getInstance
. Le code suivant montre un exemple
d'implémentation classique d'un patron de conception singleton.
<?php
class Database
{
const DB_DSN = 'mysql:host=localhost;port=3306;dbname=afsy';
const DB_USER = 'root';
const DB_PWD = 'root';
private $dbh;
static private $instance;
private function __construct()
{
}
private function __clone()
{
}
private function connect()
{
if ($this->dbh instanceOf PDO) {
return;
}
$this->dbh = new PDO(self::DB_DSN, self::DB_USER, self::DB_PWD, array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_AUTOCOMMIT => false,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'",
));
}
public function execute($sql, array $data)
{
$this->connect();
$stmt = $this->dbh->prepare($sql);
$stmt->execute($data);
return $stmt->rowCount();
}
public function query($sql)
{
$this->connect();
return $this->dbh->query($sql);
}
public static function getInstance()
{
if (null !== self::$instance) {
return self::$instance;
}
self::$instance = new self();
return self::$instance;
}
}
Ce code est l'illustration typique d'une classe capable de servir une seule
et unique même instance. En effet, toutes les caractéristiques techniques
d'implémentation d'un singleton sont ici réunies :
-
Une propriété statique privée
$instance
qui stocke une
référence à l'instance unique de cette classe,
-
Un constructeur privé afin d'empêcher la création de plusieurs instances
par instanciation directe avec le mot clé
new
,
-
Une méthode
__clone
privée afin d'empêcher la création de
plusieurs instances par clonage de l'instance unique avec le mot clé
clone
,
-
Une méthode statique
getInstance
qui crée qu'une seule et
unique instance de cette classe.
Mais alors en quoi cela pose-t-il problème d'utiliser un singleton lorsque
le code a besoin que d'une et une seule instance d'une même classe ? A
première vue, il semble n'y avoir que des avantages comme le montre la
classe ci-après :
<?php
class UserGateway
{
public function insert($username, $password)
{
$db = Database::getInstance();
$db->execute(
'INSERT INTO `user` (`username`, `password`) VALUES (?,?)',
array($username, $password)
);
}
public function update($id, $username, $password)
{
$db = Database::getInstance();
$db->execute(
'UPDATE `user` SET `username` = ?, `password` = ? WHERE `id` = ?',
array($username, $password, $id)
);
}
}
La connexion à la base de données est récupérée grâce à la méthode statique
getInstance
de la classe Database
. A première vue,
c'est extrêmement pratique mais en réalité l'usage de ce singleton pose
plusieurs problèmes majeurs.
Bien que ce soit le but premier du patron singleton que de contrôler qu'il
existe qu'une seule et unique instance de la classe, il s'agit aussi d'une
sévère limitation. En effet, il se pourrait qu'un jour l'application doive
interagir avec plusieurs bases de données grâce aux mécanismes de
réplication des données. Dans ce cas, les requêtes SQL de type
INSERT
, UPDATE
et DELETE
devront être
dirigées sur la connexion maître tandis que les requêtes de type
SELECT
seront quant à elles exécutées sur les bases de données
configurées comme esclaves.
Le patron de conception singleton ne facilite pas non plus la configuration
des objets. Dans notre exemple, la connexion à la base de données est
configurée par l'intermédiaire de constantes de classe. Avec cette
implémentation, il est impossible de changer aisément les identifiants
d'accès à la base de données sans devoir toucher au code. Une solution
consisterait à passer des arguments à la méthode getInstance
mais cela impliquerait de changer tous les appels à cette méthode partout
dans le code. Un tel changement induirait de nombreuses duplications de
code.
Il faut aussi savoir que les singletons sont comparables à des variables
globales de l'application. En effet, un singleton est accessible depuis
n'importe où et à n'importe quel moment dans le code. Si l'état d'un
singleton est modifiable à tout moment, par l'intermédiaire de mutateurs
(setters) par exemple, alors n'importe quelle ligne de code de
l'application pourrait modifier cet objet. Une autre instruction de
l'application exécutée plus tard recevrait alors un singleton avec un état
inattendu, ce qui pourrait avoir des conséquences nuisibles.
Enfin, les singletons introduisent des couplages forts
entre les objets. Dans l'exemple de cet article, la classe
UserGateway
est très fortement liée à la classe concrète
Database
en raison de l'appel statique. Ce couplage fort a pour
conséquence directe d'empêcher le changement de cette dépendance par une
autre, notamment à l'occasion de l'écriture de tests unitaires. En effet,
tester unitairement la classe UserGateway
oblige forcément de
se connecter à une base de données réelle. Il est ici impossible de se
connecter à une base de données de tests dediée ou de simuler une connexion
à une base de données.
En somme, les singletons ne facilitent ni l'extensibilité du code, ni le
découplage des composants, ni la configurabilité des modules et encore moins
les tests unitaires. Autant de raisons pour inciter les développeurs à ne
surtout pas les utiliser.
Couplage fort
Le couplage se traduit par les dépendances qui existent entre plusieurs
modules. Dans l'exemple de la classe UserGateway
, on repère
très clairement le couplage de celle-ci avec le composant
Database
. Cela signifie que si l'on doit modifier la classe
Database
, alors il y aura un risque que ce changement impacte
la classe UserGateway
... et réciproquement.
Ce couplage est d'ailleurs d'autant plus fort du fait de l'utilisation du
singleton. Il empêche complètement d'utiliser une autre instance de la
classe Database
dans la classe UserGateway
. Les
tests unitaires seront donc par exemple particulièrement difficiles à mettre
en oeuvre avec ce type de code.
Incapacité à tester le code
Les deux sections précédentes ont montré comment l'utilisation de singletons
ainsi qu'un fort couplage entre les composants ont des incidences sur la
testabilité du code. Un code fortement couplé sera difficilement testable,
voire dans certains cas, intestable !
Il est important de tester unitairement le code afin de garantir qu'il
fonctionne et prévenir aussi les éventuels bogues, cas limites et
régressions. Un code bien testé est en effet plus robuste aux changements
pendant tout son cycle de vie (développement, maintenance, évolution,
déploiement...). Les tests unitaires facilitent la vie du développeur et
permettent de détecter de nombreux problèmes en amont, bien avant qu'ils se
produisent.
Pour être testé, le code doit bien sûr être testable. Par
conséquent, cela implique de suivre un certain nombre de bonnes pratiques
pour y parvenir. Le principe SOLID présenté dans la suite de cet
article est un moyen efficace pour rendre son code testable.
Optimisations prématurées
Optimiser le code trop tôt dans un but hypothétique de le rendre plus
performant ou parfois plus concis est toujours un effort vain. En effet, les
optimisations prématurées du code rendent la lecture et la compréhension de
ce dernier plus difficiles.
Il est souvent plus sage de sacrifier ces optimisations pour sauver
d'hypothétiques millisecondes au profit d'un code clair et compréhensible.
Il convient d'optimiser au moment opportun lorsque ce besoin se fait
réellement sentir. Des outils de profilage avancés comme
xdebug ou
xhprof aident à identifier
précisément les zones du code les plus propices à d'éventuelles
optimisations.
Nommage indéchiffrable
Un nommage simple et compréhensible du code, et c'est un temps précieux de
gagner lorsqu'il s'agit de le relire, le maintenir ou le faire évoluer. Cela
semble évident mais c'est sans doute l'une des tâches des plus compliquées
pour un développeur.
Savoir nommer correctement une variable, une fonction, une méthode, une
classe ou encore un espace de nommage n'est pas toujours facile. Encore
moins quand on ne maîtrise pas le métier de l'application que l'on
développe. Et pourtant, un bon nommage apporte la sémantique au code. Enfin,
il est fortement déconseillé de faire usage d'abréviations ou d'acronymes
dans le code car tout le monde ne connaît pas forcément le sens exact de ces
derniers.
Symfony est un très bel exemple de logiciel dans lequel les développeurs
s'appliquent à nommer correctement les choses. Beaucoup d'utilisateurs du
framework ne le savent pas mais le choix définitif des noms
Symfony\Bridge
, OptionsResolver
ou encore
ExpressionLanguage
ont nécessité des moments de brainstorming
participatif avec la communauté des développeurs.
Duplications
Que dire sur ce principe tellement il semble évident ? Il s'agit de garder
le code KISS (Keep It Simple Stupid) et DRY
(Don't Repeat Yourself). Les duplications de code à travers le code
augmentent le temps de maintenance et les risques de bogues. Des lignes de
code dupliquées et c'est de la dette technique qui s'ajoute encore à votre
application. Une modification du code nécessite de répercuter le changement
à plusieurs endroits.
Des méthodes simples existent pour réduire les duplications du code. Dans un
ensemble de classes, il s'agit par exemple de profiter de l'héritage ou de
la composition en isolant les redondances dans des classes, traits et
méthodes. Dans une vue Twig, il convient par exemple de s'appuyer sur les
macros ou bien les marqueurs include
et embed
en
isolant les bouts de code dupliqués dans des templates.
Des outils comme PHP Copy Paste Detector
(PHPCPD) ou
SonarSource automatisent la détection
de duplications de code. Enfin l'utilisation de bibliothèques tierces
écrites par d'autres sont un bon moyen de remplacer de réutiliser du code et
factoriser certaines parties redondantes d'un code. Avec
Composer et
Packagist, il est devenu aujourd'hui très
facile de trouver et d'installer des bibliothèques de code réutilisables
éprouvées.
Le principe SOLID
SOLID est l'acronyme pour les cinq principes suivants :
-
Principe de responsabilité unique
(Single Responsability Principle),
-
Principe ouvert / fermé
(Open Close Principle),
-
Principe de substitution de Liskov
(Liskov Substitution Principle),
-
Principe de ségrégation d'interfaces
(Interface Segregation Principle),
-
Principe d'injection de dépendance
(Dependency Injection Principle).
Ce grand principe de la programmation orientée objet a été inventé par
Michael Feathers et Robert C. Martin
(aka Uncle Bob) au début des années 2000. SOLID définit
cinq bonnes pratiques orientées objet à appliquer au code afin d'en
simplifier la maintenance, la testabilité et les évolutions futures.
Principe de responsabilité unique (SRP)
A class should have one, and only one, reason to change.
Robert C. Martin.
D'après Robert C. Martin, le principe de responsabilité unique stipule
qu'une classe doit avoir une et une seule raison de changer. En
d'autres termes, une classe doit remplir un rôle précis. C'est exactement
comme dans une entreprise où les tâches sont réparties entre chaque employé
en fonction de leur expertise respective.
<?php
class CsvDataImporter
{
public function import($file)
{
$records = $this->loadFile($file);
$this->importData($records);
}
private function loadFile($file)
{
$records = array();
if (false !== $handle = fopen($file, 'r')) {
while ($record = fgetcsv($handle)) {
$records[] = $record;
}
}
fclose($handle);
return $records;
}
private function importData(array $records)
{
try {
$this->db->beginTransaction();
foreach ($records as $record) {
$stmt = $this->db->prepare('INSERT INTO ...');
$stmt->execute($record);
}
$this->db->commit();
} catch (PDOException $e) {
$this->db->rollback();
throw $e;
}
}
}
Dans cet exemple, la classe CsvDataImporter
a pour rôle
d'importer des données issues d'un fichier au format CSV. Au premier abord,
cette classe semble tout à fait correcte. Cependant, pour un développeur
expérimenté, il est évident que cette classe possède plus d'une
responsabilité. En effet, la classe CsvDataImporter
réalise
deux tâches de nature complètement différente :
- Lire un fichier CSV et transformer les données en tableaux PHP,
- Importer ces enregistrements dans une base de données MySQL.
Il y a donc clairement deux raisons que la classe change dans un futur
proche. La première est le changement du format de sérialisation des données
tandis que la deuxième concerne le moyen de stockage de ces dernières. En
effet, il faudra modifier la méthode loadFile
si demain les
données sont issues d'un fichier XML ou JSON. Aussi une réécriture de la
méthode importData
sera nécessaire s'il est question de charger
ces données dans un Mongodb
par exemple.
La solution pour se conformer au principe de responsabilité unique consiste
à décomposer la classe CsvDataImporter
en deux sous-classes :
CsvFileLoader
et DataGateway
. La nouvelle classe
générique DataImporter
n'a alors plus qu'à déléguer ces deux
tâches à ses deux dépendances.
<?php
class DataImporter
{
private $loader;
private $gateway;
public function __construct(FileLoader $loader, Gateway $gateway)
{
$this->loader = $loader;
$this->gateway = $gateway;
}
public function import($file)
{
foreach ($this->loader->load($file) as $record) {
$this->gateway->insert($record);
}
}
}
Note : les types des dépendances dans le constructeur de la classe
DataImporter
sont ici des classes abstraites ou des
interfaces.
Avec ce découpage en trois petites classes, il est désormais plus facile de
tester unitairement chaque objet, de faire évoluer les implémentations
existantes ou d'en ajouter de nouvelles.
Principe ouvert / fermé (OCP)
You should be able to extend a classes behavior, without modifying it.
Robert C. Martin.
Le principe ouvert / fermé consiste à rendre les modules ouverts à
l'extension et fermés aux modifications. En d'autres termes,
il s'agit de pouvoir enrichir aisément les fonctionnalités d'un module sans
avoir à en modifier son comportement.
Le dernier exemple présenté à la fin du principe de responsabilité unique se
conforme en effet au principe ouvert / fermé. En effet, il est très facile
de supporter de nouveaux formats de sérialisation des données ainsi que de
nouveaux adapteurs pour des systèmes de stockage. Il s'agit tout simplement
de créer de nouvelles classes respectant les contrats des interfaces sans
avoir à changer la moindre classe existante.
$importer = new DataImporter(new CsvFileLoader(), new MySQLGateway());
$importer = new DataImporter(new XmlFileLoader(), new MongoGateway());
$importer = new DataImporter(new JsonFileLoader(), new ElasticSearchGateway());
Comme le montre le code ci-dessus, l'objet DataImporter
n'a pas
été modifié. Il s'agit juste de lui injecter de nouvelles implémentations
des interfaces FileLoader
et Gateway
afin de
pouvoir utiliser par exemple des données sérialisées en JSON à insérer dans
une base MongoDB. Inutile de changer l'implémentation interne de la classe
DataImporter
pour y parvenir.
Principe de substitution de Liskov (LSP)
Derived classes must be substitutable for their base classes.
Robert C. Martin.
Le principe de substitution de Liskov indique qu'il doit être possible pour
un objet de type T
acceptant une dépendance de type
S
, de pouvoir remplacer cette dernière par une dépendance d'un
type dérivé de S
sans que cela est le moindre impact sur le
fonctionnement du code.
Derrière cette définition aux airs savants se cache en réalité un principe
fondamental de la conception orientée objet : l'héritage. Il s'agit en
réalité de toujours conserver les signatures des méthodes d'une classe
parent dérivée ainsi que la nature des valeurs de retour de ces
dernières.
En PHP, il est obligatoire d'implémenter strictement les mêmes signatures
des méthodes de la classe parent lorsque celle-ci est spécialisée et que ses
méthodes sont redéfinies. En revanche, comme PHP est un langage faiblemenent
typé, il est possible de retourner n'importe quel type de valeur en sortie
d'une méthode.
Le principe de substitution de Liskov impose donc de s'assurer que la valeur
retournée par une méthode redéfinie, est bien du même type que celle
initialement retournée par la méthode de la classe parente.
<?php
abstract class AbstractLoader implements FileLoader
{
public function load($file)
{
if (!file_exists($file)) {
throw new \InvalidArgumentException(sprintf('%s does not exist.', $file));
}
return [];
}
}
class CsvFileLoader extends AbstractLoader
{
public function load($file)
{
$records = parent::load($file);
if (false !== $handle = fopen($file, 'r')) {
while ($record = fgetcsv($handle)) {
$records[] = $record;
}
}
fclose($handle);
return $records;
}
}
Dans l'exemple de code ci-dessus, la classe CsvFileLoader
hérite de la classe abstraite AbstractLoader
et redéfinit sa
méthode load
. La signature de la méthode est respectée ainsi
que les types de retour. Dans les deux classes, la méthode load
est programmée pour retourner un tableau d'enregistrements.
Si toutes les classes concrètes dérivant la classe
AbstractLoader
conservent les mêmes types de paramètres
d'entrée et de sortie, alors c'est qu'elles s'engagent à respecter le
contrat de l'interface FileLoader
. Par conséquent, il est
possible de remplacer un objet CsvFileLoader
par une instance
de la classe XmlFileLoader
dans le constructeur de la classe
DataImporter
.
En pratique dans la vie quotidienne, c'est comme changer un pneu crevé d'une
marque X par un autre pneu d'une marque Y. En effet, les fabricants de
pneumatiques suivent les spécifications techniques standardisées des
constructeurs automobiles. En programmation orientée objet, une interface
est une spécification technique qui définit un modèle d'implémentation.
Le principe de ségrégation d'interfaces (ISP)
Make fine grained interfaces that are client specific.
Robert C. Martin.
Le principe de ségrégation d'interfaces est identique au principe de
responsabilité unique des classes (SRP), mais à la différence qu'il
s'applique aux interfaces. Le principe de responsabilité unique stipule
qu'une classe doit avoir une seule responsabilité. Eh bien c'est la même
chose pour une interface qui se doit d'être la plus petite possible et
représenter l'implémentation d'une seule tâche.
L'avantage des interfaces par rapport aux classes en PHP, c'est qu'elles se
composent facilement grâce à l'héritage. En PHP, une classe peut dériver
qu'une seule classe parente à la fois. En revanche, une interface peut
elle-même dériver une, voire plusieurs interfaces à la fois. L'héritage
multiple existe bel et bien en PHP !
Un très bel exemple de composition d'interfaces par héritage multiple existe
dans le framework Symfony. Il s'agit de l'interface
RouterInterface
implémentée par la classe
Router
.
Le routeur de Symfony a pour rôle de générer des urls et de faire
correspondre une url à des paramètres de route. C'est pour cette raison que
la classe Router
définit les deux méthodes publiques
generate
et match
.
A première vue, ces deux méthodes peuvent être réunies dans une interface
commune RouterInterface
. Cependant, le routeur peut être
injecté à divers endroits dans le framework pour réaliser ces tâches
séparément et donc unitairement. Le fait de n'avoir besoin que de la méthode
generate
ou de la méthode match
à un instant T
dans un objet justifie donc de décomposer plus finement cette
RouterInterface
en deux plus petites interfaces.
<?php
interface UrlGeneratorInterface
{
public function generate($name, $parameters = array());
}
interface UrlMatcherInterface
{
public function match($pathinfo);
}
interface RouterInterface extends UrlMatcherInterface, UrlGeneratorInterface
{
public function getRouteCollection();
}
Ainsi, par exemple, il devient possible de typer un argument uniquement avec
l'interface UrlGeneratorInterface
quand une classe a besoin
d'appeler seulement la méthode generate
sur cet argument. C'est
d'ailleurs exactement ce qui est fait dans la classe d'extension Twig
RoutingExtension
qui reçoit dans son constructeur le routeur
afin de permettre aux fonctions Twig path
et url
de générer des chemins.
<?php
namespace Symfony\Bridge\Twig\Extension;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class RoutingExtension extends \Twig_Extension
{
private $generator;
public function __construct(UrlGeneratorInterface $generator)
{
$this->generator = $generator;
}
public function getPath($name, $parameters = array())
{
return $this->generator->generate($name, $parameters);
}
}
Dans cet exemple, l'argument $generator
peut être aussi bien le
routeur ou bien un autre objet dédié à cette responsabilité de générer des
urls.
Un dernier avantage à fractionner des interfaces en petites interfaces
concerne la testabilité du code. En effet, plus les interfaces sont petites,
et plus il est facile de tester leurs implémentations. Aussi, dans un
framework de tests unitaires comme PHPUnit, il est bien plus facile de «
mocker » une interface plutôt qu'une classe concrète ou abstraite.
Le principe d'injection de dépendance (DIP)
Depend on abstractions, not on concretions.
Robert C. Martin.
Enfin, le principe d'injection de dépendance (aussi appelé principe
d'inversion des dépendances) stipule qu'il faille programmer par rapport à
des abstractions plutôt que des implémentations.
Le code ci-dessous réalise complètement l'inverse puisque la classe
DataImporter
dépend directement de deux implémentations
concrètes du fait de l'instanciation des deux classes
CsvFileLoader
et DataGateway
.
<?php
class DataImporter
{
private $loader;
private $gateway;
public function __construct()
{
$this->loader = new CsvFileLoader();
$this->gateway = new DataGateway();
}
}
Instancier les dépendances directement à l'intérieur du constructeur limite
considérablement les capacités à étendre le code mais aussi à le tester. En
effet, en codant en dur une instanciation avec le mot clé new
,
la classe DataImporter
devient fortement couplée à sa
dépendance CsvFileLoader
. Cela signifie aussi qu'il est
impossible de remplacer cette dépendance par une autre pour un besoin
ultérieur. Aussi cela empêche de tester unitairement la classe
DataImporter
puisque les dépendances ne peuvent être remplacées
par des doublures (« mocks »).
Pour se conformer au principe d'injection de dépendances, il s'agit tout
simplement de créer les deux dépendances de la classe
DataImporter
à l'extérieur de celle-ci, puis de les injecter
dans le constructeur. Cela a pour effet immédiat d'éliminer l'utilisation du
mot clé new
et donc de réduire le couplage entre les
composants.
<?php
class DataImporter
{
private $loader;
private $gateway;
public function __construct(CsvFileLoader $loader, DataGateway $gateway)
{
$this->loader = $loader;
$this->gateway = $gateway;
}
}
Bien que cette classe reçoive désormais ses dépendances par l'intermédiaire
de son constructeur, elle reste néanmoins fortement couplée aux deux
implémentations injectées. En effet, les arguments du constructeur sont ici
typés avec des classes concrètes (donc des implémentations). En typant un
paramètre avec une classe concrète (ou abstraite également), cela oblige à
injecter forcément une instance de cette classe ou bien une instance d'une
classe dérivée. C'est du au fait que PHP supporte uniquement de l'héritage
simple pour les classes.
Pour rappel, le principe d'injection de dépendance stipule qu'une classe
doit dépendre d'abstractions et non d'implémentations. Par conséquent, il
s'agit de remplacer le typage des arguments par des interfaces au lieu de
classes.
<?php
class DataImporter
{
private $loader;
private $gateway;
public function __construct(FileLoader $loader, Gateway $gateway)
{
$this->loader = $loader;
$this->gateway = $gateway;
}
}
Ici les interfaces FileLoader
et Gateway
favorisent l'injection de n'importe quels objets implémentant ces
dernières.
Conclusion
STUPID et SOLID sont deux principes qui s'opposent. Le
premier est un ensemble de mauvaises pratiques et d'écueils à éviter
absolument tandis que l'autre invite à suivre les bonnes pratiques. Avec ces
deux outils en sa possession, le développeur pourra facilement identifier
les problèmes dans son code et trouver les remèdes pour les corriger.