Complexité, facilité et simplicité, un triptyque paradoxal qu’il convient de concilier
Notre quotidien consiste à gérer la complexité en évaluant les impacts d’un nouveau composant ou d’une nouvelle version, en établissant un plan d’action pour une montée de version, en réagissant lorsqu’un changement provoque des conséquences imprévues. Cette complexité semble inhérente à n’importe quel projet, on le sait, comme une fatalité, avec le temps, un projet devient difficile à faire évoluer, et même à maintenir en condition opérationnelle.
Pour qu’un projet reste maintenable et conserve son potentiel d’évolution, il est nécessaire de tendre vers la plus grande simplicité. Cela semble évident au premier abord. On pourrait penser que chacun d’entre nous souhaite appliquer la solution la plus simple à un problème donné. En effet, il n’existe pas de raison de tendre systématiquement vers la complexité…
Malheureusement, penser que tout à chacun tende vers la recherche de la solution la plus simple, c’est commettre une erreur fondamentale, en confondant les notions de facilité et de simplicité. Tendre vers la solution la plus facile est une évidence biologique. Pour les animaux que nous sommes, économiser son énergie c’est favoriser sa survie. La facilité est une question d’effort alors que la simplicité est une question de conception que l’on pourrait définir par le fait d’avoir peu de composants et surtout peu de liens entre ceux-ci. Parfois, la solution la plus simple est aussi la plus facile. Parfois, en revanche ça n’est pas le cas.
Nous pouvons citer un exemple où simplicité et facilité vont de pair : “l’architecture web classique”. Difficile de faire plus simple que de stocker les données dans une base de données et stocker la logique et l’interface dans une application.
Un contre-exemple : je dispose d’une application sur laquelle les utilisateurs internes d’une organisation s’authentifient via un SSO, annuaire, ou autre. J’ai besoin qu’un prestataire extérieur puisse accéder à cette application. La solution la plus facile serait probablement de créer un compte local dans l’application à cet utilisateur, sans passer par un annuaire ou un SSO. C’est facile… mais ça n’est pas simple. On se retrouve dans une situation qui contraint à gérer le cycle de vie des comptes locaux. Le passage à l’échelle (plusieurs prestataires, plusieurs applications… ) va vite devenir une problématique forte.
La complexité vient de l’amoncellement de futures petites actions pour lesquelles la facilité est priorisée, car toujours urgentes, mais qui ne sont pas coordonnées et qui créent peu à peu de la lourdeur et irrémédiablement à la fois de la dette technique et un surcroît de gestion.
Comment « faire simple »
Nous l’avons vu, la simplicité ne vient pas naturellement, y accéder demande de la discipline. Pour ce faire, voyons quelques angles d’attaque
- Conception :
La simplicité d’un système se décide et se joue souvent dès la conception. Celle-ci doit favoriser le fait d’avoir peu de composants, qu’ils soient complémentaires (pour éviter les redondances qui dupliquent le travail de maintenance) et que leurs interactions soient peu nombreuses et bien définies (pour limiter les effets de bords dus à des interactions inconnues).
Dans un tel système, une petite équipe peut se concentrer sur un seul composant et n’a besoin d’interagir qu’avec les quelques équipes dont le composant dépend directement. La philosophie UNIX est l’exemple le plus connu de conception basée sur la simplicité (KISS). L’UNIX original comprend peu de commandes et d’appels systèmes et il y a peu de mécanismes d’interaction (descripteurs de fichiers, signaux).
- Sobriété :
Difficile de faire plus simple qu’un système qui n’existe pas. Dès la conception, renoncer à certaines fonctionnalités marginales ou supprimer celles qui ne sont plus nécessaires, permet mécaniquement de réduire le nombre de composants, le volume de code ainsi que la consommation de ressources. A-t-on besoin d’implémenter un cas d’usage qui se produit une fois par an et qui peut être traité par un administrateur manuellement ? A-t-on vraiment besoin de stocker des données personnelles qui amènent avec elles leur lot de contraintes techniques et légales ? Est-il vraiment utile d’implémenter un système de remontée automatique d’alertes dans le nouveau logiciel alors que le monitoring en place le fait déjà ?
- Extensibilité :
Le fait de pouvoir améliorer un système, qu’il s’agisse de sa modification directe ou via un système de plugin, permet souvent d’économiser un composant supplémentaire qui aurait servi de passerelle.
Exemple : un plugin permettant aux utilisateurs AD de se connecter à une application permet d’économiser la mise en place d’un batch d’import des utilisateurs, avec synchronisation des mots de passe, gestion des suppressions.
- Interopérabilité et standards :
Favoriser le respect de standards établis et l’interopérabilité entre solutions permet de s’abstraire d’une technologie particulière. La dépendance à un fournisseur donné est ainsi réduite, mais ça n’est pas le seul avantage. Pouvoir remplacer un composant par un autre est indispensable à des fins de tests (bouchons, etc.) et renforce l’assurance qu’un composant testé en isolation fonctionnera comme attendu une fois intégré.
HTTP est un excellent exemple : de nouveaux usages de HTTP apparaissent constamment, facilités par le fait qu’il n’y a pas besoin de modifier les clients et les serveurs existants. Lorsque l’on développe un protocole basé sur HTTP, il n’est pas nécessaire de tester la gestion des connexions réseau par les bibliothèques qui implémentent ce protocole ; on sait que ça fonctionne.
- Long terme :
On l’a vu, la simplicité est parfois à l’opposé de la facilité. Elle a donc parfois un coût de mise en œuvre plus important. Cependant, ce surcoût est vite rentabilisé, car l’équipe de maintenance reste de taille modeste, le projet reste facile à appréhender et les évolutions ont à la fois moins d’impact et restent plus faciles à tester.
- Refactoring
On prend rarement, dans un projet, le temps nécessaire à la consolidation des différentes évolutions et notamment supprimer ce qui n’a plus lieu d’être. En d’autres mots faire du nettoyage. Les développeurs agiles parlent de réusinage/refactoring, une étape essentielle qui est très souvent négligée. Le refactoring s’applique en réalité à tous les domaines. Par exemple, si un load balancer a été installé pour le projet X, il ne faudra pas oublier de le supprimer si un load balancer mutualisé est mis en place plus tard.
Le salut via l’Open Source ?
Le fait que le code d’un logiciel soit ouvert ou non n’a pas une influence directe sur son caractère de “simplicité”. On trouve des logiciels simples et bien conçus mais privateurs et on trouve des amas de complexité à des codes parfaitement libres. N’insistez pas, nous ne donnerons pas d’exemples…
Pourtant le mode de développement communautaire de l’Open Source encourage la simplicité du design : de petites équipes, souvent peu coordonnées entre elles qui développent des solutions basées sur des standards d’interopérabilité. La bonne maintenance du logiciel est assurée par le fait que les développeurs ne disposent parfois que de ressources limitées, et la sobriété est garantie par le fait que bien souvent, c’est à celui qui désire une fonctionnalité de faire l’effort de l’implémenter. Gare à celui qui augmentera trop la complexité d’un logiciel existant en introduisant une nouvelle fonctionnalité, les mainteneurs seront particulièrement vigilants à ce que cela n’arrive pas, puisque justement, ce sera à eux que reviendra la tâche de la maintenir.
Conclusion
Nous sommes convaincus, peut-être que vous aussi maintenant, que la simplicité doit redevenir une priorité dans les projets informatiques. Cet effort sera récompensé par un gain de réactivité concernant les évolutions et apportera cohérence et maintenabilité sur le long terme. Un système d’information simplifié permet tout autant aux équipes d’alléger leur surcharge cognitive que de mieux anticiper les impacts de leurs actions.
“Faisons l’effort de faire plus simple !”