Découplez votre code
... vos collègues vous remercieront.
Le découplage n'est pas un concept nouveau mais cela ne fait pas longtemps que je me documente sur le sujet. Je vous mets ici ce que j'ai appris.
Qu'est que le "couplage" ?
Pour comprendre le découplage il faut déjà savoir ce qu'est le couplage du code.
Il y a une notion de couplage lorsque deux services aux responsabilités indépendantes ont besoin l'un de l'autre pour fonctionner. Ces deux services sont dit "couplés".
Notre façon de concevoir nos classes va augmenter ou diminuer la force de ce couplage :
- un couplage fort est fait lorsque des classes sont initialisées à l’intérieur d'autres classes, par exemple. De manière générale, utiliser directement nos classes augmente le couplage.
- un couplage faible existe lorsque la première classe ne sait pas qui est et comment fonctionne exactement la classe qu'elle utilise.
Exemple de code couplé :
use Config/IniConf;
class IndexController
{
private $iniConf;
public function __construct()
{
$this->iniConf = new IniConf('../config.ini');
}
public function renderView()
{
echo '[' . $this->iniConf->getValue('db') . ' : ' . count($this->iniConf->getValue('db')) . ']';
echo '[' . $this->iniConf->getValue('name') . ' : 0' . ']';
}
}
Mon contrôleur a besoin d'accéder aux paramètres de l'application qui sont stockés dans un fichier .ini
. Pas de problème, j'ai justement une classe qui s'occupe de ça : IniConf
, je l'utilise donc. Et je fais de même dans plusieurs contrôleurs AboutController
, AdminController
, PortfolioController
. Nickel.
Mais qu'en est-il si demain je décide de changer l'adresse de mon fichier de configuration ? Ou s'il me vient l'idée de stocker les paramètres dans une base de données pour plus de flexibilité ? Vous voyez où je veux en venir ? À chaque fois il faut que je modifie mon/mes contrôleurs.
Le nombre de lignes de code à modifier peut vite devenir exponentiel : je vais passer plus de temps à modifier mes contrôleurs qu'à écrire la classe de stockage des configurations en base de données.
Le "découplage" est la solution
Comment découpler le code pour limiter au maximum ce type de problème ?
On peut déjà séparer IniConf
et IndexController
. Bien-sûr que IndexController
a besoin de IniConf
(sa dépendance) pour fonctionner mais ça ne devrait pas être à lui de l'initialiser. On va donc faire de l'injection de dépendance :
// Constructeur IndexController
public function __contruct($iniConf) {
this->iniConf = $iniConf;
}
Voilà. Je peux ainsi instancier IniConf
une fois et le passer aux constructeurs de mes contrôleurs. Cela limitera déjà beaucoup le nombre d'endroits où je devrai modifier l'adresse du fichier.
Pour notre problème de changement complet de la méthode de sauvegarde des paramètres de l'application; reprenons une phrase plus haut :
la première classe ne sait pas qui est et comment fonctionne exactement la classe qu'elle utilise
En effet, elle doit juste savoir le type de classe dont elle a besoin et comment ce type fonctionne. Pour cela on va établir un contrat entre IndexController
et IniConf
.
La façon de le faire varie selon les langages : en PHP on utilisera les interfaces pour ça, en Swift ce seront les protocoles.
// Contrat
namespace Config;
interface Configurator
{
public function set($key, $value);
public function get($key);
}
Quelque soit ma méthode de stockage de paramètres, je veux pouvoir accéder aux paramètres et pouvoir modifier les clés. On crée donc une interface Configurator
qui exige cela.
use Config/Configurator;
class IndexController
{
private $conf;
public function __construct(Configurator $conf)
{
$this->conf = $conf;
}
public function renderView()
{
echo '[' . $this->conf->get('db') . ' : ' . count($this->conf->get('db')) . ']';
echo '[' . $this->conf->get('name') . ' : 0' . ']';
}
}
Je modifie le code de mes contrôleurs en conséquence :
- Le constructeur demande un objet qui respecte le contrat
Configurator
- Le contrôleur utilise les méthodes du contrat plutôt que des méthodes propre à une classe.
- Le contrôleur n'utilise plus forcément
IniConf
, il ne sait pas (ce qu'il utilise) en fait, on change donc l'attribut en$this->conf
La dernière action à faire est de modifier le code de IniConf
pour qu'il respecte le contrat Configurator
.
Conclusion
Le code est beau, il est maintenant bien découplé. Rien à changer au niveau du résultat, le nouveau code fait exactement la même chose que le précédent mais il est tellement plus simple à maintenir...
Le code de mon index.php
:
$conf = new IniConf('../config.ini');
$index = new IndexController($conf);
$other = new OtherController($conf);
$index->renderView();
La beauté de l'injection de dépendance.
Je dois modifier l'adresse du fichier de conf :
$conf = new IniConf('../libs/config.ini');
$index = new IndexController($conf);
$other = new OtherController($conf);
$index->renderView();
L'adresse est modifiée à un unique endroit et profite à toute mon application.
Je migre ma config en base de données.
$conf = new BDDConf([
'dbname' => 'config',
'login' => 'dbuser',
'passwd' => '1234'
]);
$index = new IndexController($conf);
$other = new OtherController($conf);
$index->renderView();
Le changement est transparent pour mes contrôleurs, je ne change aucun code. Je me suis concentré sur ma nouvelle fonctionnalité (écriture de la classe pour la nouvelle méthode de stockage) et je n'ai pas perdu de temps à modifier toutes les lignes de code de mon application où apparaissait IniConf
.
Pour ceux qui souhaitent jeter un oeil au code de façon plus poussé, voici le lien github du projet.