10 principes de conception de systèmes logiciels

Ma participation a des projets de réalisation de logiciels m'amène souvent à utiliser ou rappeler quelques principes fondamentaux de conception. J'ai souhaité à travers cet article partager ma vision de cette activité. Ces principes sont en fait assez génériques et peuvent souvent être appliqués quelque soit le niveau d'abstraction de l'objet à réaliser (architecture applicative, architecture technique, implémentation en Java ou Ruby, etc.). On retrouve bien sûr les principes de base du paradigme objet ainsi que d'autres plus générales et d'autres plus pragmatiques.

J'essaye de résumer chacun de ces principes par une phrase simple à l'impératif présent avec uniquement un verbe et un complément d'objet. Ceci permet de rendre ces règles courtes et facilement mémorisables. Elles pourraient être la base d'un mantra du concepteur/développeur :-)

"Encapsulez l'implémentation"

Ce principe de base du paradigme objet, également appelé Information hiding , implique une séparation nette entre l'interface d'utilisation d'un composant logiciel et son implémentation. Cette dernière doit être complétement inaccessible à l'utilisateur du composant. J'utilise le terme "composant logiciel" car celui ci peut prendre des formes diverses et variées : une fonction, un objet, etc. L'utilisation du principe d'interface, comme en Java, permet de physiquement séparer ces deux éléments.

"Séparez les responsabilités"

En anglais Separation of concerns . Principe fondamental permettant de modulariser un système en séparant clairement les rôles que tiennent chacun des composants de ce système. Cette séparation peut être arbitraire mais on peut néanmoins identifier des métriques permettant de déterminer les déséquilibres de responsabilités dans un système (nombre de classes élevées dans un package, nombre de lignes dans une fonction, nombre d'opérations dans une classes)
On peut aboutir à une identification efficace des responsabilités, notamment en conception objet par l'utilisation de techniques comme les CRC cards . Dans le cadre d'une application on trouve souvent au minimum la séparation des responsabilités techniques entre les fonctions métiers et IHM avec le pattern MVC

"Minimisez les dépendances spatiales, temporelles et structurelles"

Principe, à mon avis, extrêmement fondamental qui impose de définir clairement quelles sont les dépendances d'un composant. Pour chaque dépendance on tentera de déterminer ses différentes qualités décrites ci-après. L'objectif étant pour chacunes de ses dépendances de les minimiser et ainsi d'appliquer le principe de couplage faible .

"Réduisez les états"

C'est pour moi le principe fondamental permettant la réduction des causes d'erreurs en diminuant le nombre d'état concurrents dans un système à un instant t. Idéalement, les zones de stockage d'état d'un système logiciel peuvent se réduire à la pile d'exécution et la base de données.
On retrouve souvent une troisième zone dénommée Session dans les développements web, notamment en Java et en .Net, mais absente de PHP dans son modèle de programmation par défaut.
Ce point rejoint une des conséquences de la programmation fonctionnelle, à savoir la suppression des variables d'états par une programmation non impérative. Cet article explique clairement pourquoi les langages fonctionnels "purs" ont peu d'états concurrents de par leurs conceptions.

"Echouez rapidement"

Ce principe, en anglais Fail fast , est associé au principe Vérifiez les hypothèses . Lorsque les hypothèses ou besoins d'un traitement ne sont pas remplies (absence de paramètres, erreur lors de l'utilisation d'un autre composant) le composant doit échouer de la manière la plus explicite et incontournable possible, avec une exception par exemple. Lorsque un traitement ne se déroule pas correctement l'erreur doit être reportée immédiatement. Cette erreur peut également mettre en oeuvre le principe "Donnez de la signification" : connaitre si possible la cause de l'erreur, sa description et surtout les actions permettant de la corriger.

"Donnez de la signification"

Ce principe a pour objet de fournir la sémantique la plus claire possible à tout nommage (variable, classe, répertoire, composants, etc.). Le code ou l'environnement est ainsi auto-signifiant et n'a pas besoin d'éléments de signification supplémentaire sous forme de documentation. Si possible, comme avec l'outil javadoc en Java, le code doit inclure sa documentation. Le nommage ainsi que la signification s'affine au fûr et à mesure de la réalisation, il est donc nécessaire de pouvoir effectuer facilement des modifications dans le code source grâce aux fonctions de refactoring.

"Vérifiez les hypothèses"

Si une hypothèse vérifiée par un composant n'est pas remplie la règle Echouer rapidement est à appliquer. Ce principe est une conséquence du principe Minimisez les dépendances , chaque fois qu'une dépendance est nécessaire il faut s'assurer de sa validité à l'exécution. Chaque fois que le développeur se dit "tel composant doit me fournir tels éléments" (telles données ou une donnée formatée de manière particulière par exemple) le composant doit vérifier et lever une erreur en cas de condition non remplie. Une technique de programmation est associée à ce principe : la programmation par contrat . Ce type de programmation se rapproche également de la programmation défensive même si cette dernière vise en premier lieu à assurer un meilleur niveau de sécurité du composant.

"Paramétrez par convention"

Chaque composant nécessite des paramétres susceptible de modifier son comportement. Ces paramètres doivent chacun avoir une valeur par défaut ou pouvoir se déduire à partir du nommage d'un autre composant. Un exemple classique dans un framework MVC : la redirection vers une vue nommée "mavue" sera utilisée par le framework qui cherchera par exemple le fichier "mavue.jsp" dans son répertoire d'exécution. Le paramétrage par convention évite l'éclatement et la croissance du paramétrage d'un système.

"Protégez les variations"

Ce principe est utilisé lorsque des clients dépendent du composant construit. Il implique de prévoir les modifications d'interface afin de ne pas briser l'utilisation du composant. L'information sur l'obsolescence d'une interface est néanmoins pertinente, c'est par exemple la fonctionnalité "deprecated" fournit par le langage java et son outil javadoc. Ce principe est très lié au versionning, qui est un sujet à part entière et qui fera l'objet d'un futur article. Ce principe est souvent associé à celui dénommé Open-Closed .

"Concevez extensible et adaptable"

Ce principe est très général et permet respectivement d'enrichir ou de modifier le composant construit. Ces principes ne sont, bien sûr, pas indispensables, notamment celui d'extensibilité qui peut être assez coûteux à mettre en place. Néanmoins le principe d'adaptabilité est assez facile d'accès et peut apporter une grande évolutivité.

Conclusion

Mon billet sur les langages de programmation se concluait par l'importance qui devait être accordée à l'activité de conception. Cet article tente humblement de livrer quelques principes généraux de conception de systèmes logiciels que j'ai pu expérimenter et dont j'ai pu apprécier la puissance. Afin de compléter cet article il est intéressant de réfléchir à la manière de vérifier automatiquement chacun de ces points.