Le principe de responsabilité unique
Le principe SOLID de responsabilité unique (Single Responsibility principle ou SRP) est un principe fondamental en programmation orientée objet (POO) qui stipule qu’une unité de code (UdC) ne devrait avoir qu’une seule responsabilité.
Dit comme ça, ce principe semble simple à comprendre. Mais d’expérience, c’est certainement un des plus mal compris et maîtrisé. Par exemple, si je vous demande ce qu’on entend par “responsabilité”, qu’est-ce qui vous vient à l’esprit ?
Régulièrement, on va dire que notre UdC ne doit être en charge que d’un seul traitement. Cette définition est tout aussi vague que la précédente. Une définition plus technique consiste à dire qu’une UdC ne doit avoir qu’une seule raison de changer. La encore, ce n’est pas évident de comprendre ce qu’on entend par la notion de changement. Est-ce qu’il s’agit seulement de l’ajout d’une fonctionnalité ? Un refactoring est-il considéré comme un changement ? Qu’en est-il de la monté de version d’un lib ?
Pour mieux cerner ce principe, il faut comprendre que SRP est un principe qui concerne la répartition de la logique du code dans une application. En POO, on pourrait simplifier ça par le fait de savoir où mettre tel code dans telle classe ou module.
En fait, et c’est Bob Martin qui le dit lui-même, la meilleure question à se poser à ce sujet, c’est “A QUI?”. À qui est dédiée cette fonctionnalité ? Lorsqu’on est capable de répondre à cette question, si une UdC est en charge de répondre aux demandes de plus d’un profil d’utilisateur alors elle ne respect par le principe de responsabilité unique. Elle a donc autant de raison de changer qu’il y a de profils concernés par son utilisation.
Prenons un exemple concret:
Le classe ci-dessus présente quatre méthodes, chacune d’elles a un rôle bien défini. Il y en a une pour calculer la paie d’un salarié, une pour afficher le résultat, une pour récupérer le nombre d’heures travaillées par le salarié et enfin une pour sauvegarder le salarié en base de données.
Ici, chacune des méthodes est liée à un profil d’utilisateur différent. Le calcul de la paie est lié à la finance, l’affichage de la paie est de la responsabilité des ressources humaines, le reporting du temps de travail est le rôle des manager et enfin la sauvegarde en DB est de la responsabilité de l’équipe technique.
Si on se réfère à notre définition, quelques lignes plus haut, cette implémentation ne respect pas le principe de responsabilité unique. En effet, cette classe va changer selon l’évolution des besoins de ce quatre utilisateurs. Il y a donc bien quatre origines possibles à l’évolution de cette classe et elle à finalement plusieurs raisons de changer.
Pourtant, à première vue, cette classe semble tout à fait correcte. Alors comment faire si on souhaite respecter ce principe. Voici un exemple de code qui peut vous aider à y voir plus clair :
Ici, on réduit la classe Employee à un simple value object sans logique. La logique va être déplacée dans d’autres classes, chacune sera dédiée à une responsabilité. Voici un exemple de découpage :
Vous remarquerez ici qu’on fournit deux classes pour calculer la paye. Ces classes, qui implémentent le même contrat (PayCalculator) permettent de remplacer le if de la fonction de calcul de la version précédente.
Voici les trois dernières responsabilités qui ont été sortie. Un repository est en charge de la persistance des employés, un reporter se charge de calculer le temps de travail effectif en fonction du planning et enfin une classe qui permet d’afficher une paye en console.
Premier constat, on a plus de fichiers au bout du compte. Chaque fichier reste simple correspond à une intention bien particulière qu’il est facile de comprendre. Ici, chaque fichier est à mettre en face d’un profil d’utilisateur et n’a donc qu’une seule raison de changer.
D’un point vu technique, il est plus facile de faire évoluer le code de la deuxième version, car un changement dans l’une des classes ne pourra pas avoir d’impact sur les autres. On peut facilement remplacer une classe par une autre si on veut faire évoluer notre besoin. C’est le cas pour le calcul de la paye, mais on pourrait faire de même avec le pointer pour imprimer une fiche de paie ou envoyer le résultat par email par exemple.
Voici un petit exemple d’un calcul de paie qui s’appuie sur le découpage de nos responsabilités :
En conclusion, SRP, est un principe qui permet de garantir que le code est correctement reparti dans des UdC qui facilitent la lecture, le test et les modifications à venir. En revanche, on se retrouve avec beaucoup plus de fichiers par rapport à une approche traditionnelle basée sur les concepts réels (e.g. Employee).
Je vous invite à avoir ce principe en tête lorsque vous codez. Le gain apporté par son utilisation en termes de maintenance et d’évolution en vaut vraiment le coup.