Il existe un moment précis dans la vie d'un système logiciel où personne ne le comprend plus dans sa totalité. Ce moment passe souvent inaperçu. On continue à travailler dessus, à ajouter des fonctionnalités, à corriger des bugs — mais quelque chose a changé. Le système est devenu plus grand que l'esprit humain qui l'a conçu.
Ce n'est pas une métaphore. C'est une limite cognitive mesurable, et elle a des conséquences profondes sur la façon dont on devrait penser la conception logicielle.
Le problème de la tête
La mémoire de travail humaine peut tenir environ 7 éléments simultanément — plus ou moins deux, selon les individus et les circonstances. C'est le chiffre de George Miller, publié en 1956. La recherche plus récente suggère que ce nombre est encore plus bas pour des éléments complexes : entre 3 et 5.
Un système logiciel non trivial a des milliers de composants, de dépendances, d'invariants implicites. La relation entre ces éléments n'est pas linéaire : elle est exponentielle. Deux composants ont une relation. Dix composants ont potentiellement 45 relations. Cent composants : 4 950.
"The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise." — Edsger W. Dijkstra
L'abstraction est notre réponse à ce problème. On groupe les composants en modules, les modules en services, les services en domaines. On crée des niveaux d'indirection qui nous permettent de travailler à une échelle sans avoir à tenir l'autre dans notre tête en même temps.
Complexité accidentelle vs essentielle
Fred Brooks, dans The Mythical Man-Month, distingue deux types de complexité : essentielle et accidentelle. La complexité essentielle est inhérente au problème qu'on résout. La complexité accidentelle, c'est ce qu'on ajoute par la façon dont on le résout.
La plupart des systèmes que j'ai vus ont un ratio désastreux. Soixante, soixante-dix pour cent de leur complexité n'est pas dans le problème — elle est dans les couches d'abstractions inversées, les décisions d'architecture prises sous pression, les bibliothèques ajoutées parce que c'était à la mode.
Le coût cognitif caché
On parle souvent du coût technique de la dette technique. On parle moins du coût cognitif : le fait qu'un développeur qui rejoint un système complexe met des mois à être productif, et n'est peut-être jamais vraiment à l'aise avec l'ensemble du système.
Ce coût est réel et massif. Il ralentit les nouvelles fonctionnalités. Il génère des bugs que personne n'anticipait parce que personne ne pouvait voir l'interaction. Il décourage les gens qui auraient pu contribuer.
Ce qu'on peut réellement faire
La réponse honnête est : pas grand chose à l'échelle du système. Mais beaucoup à l'échelle de la décision individuelle.
- — Concevoir des modules qui peuvent être compris indépendamment.
- — Résister à l'envie d'ajouter de l'indirection "pour la flexibilité future".
- — Écrire du code dont la lecture révèle l'intention sans commentaires.
- — Traiter la documentation comme une contrainte de conception, pas un livrable.
Conclusion
On ne gagnera pas contre la complexité. Les systèmes utiles sont complexes — c'est dans leur nature. Mais on peut choisir quelle complexité on accepte et quelle complexité on refuse. Et cette distinction, prise de façon délibérée à chaque décision d'architecture, à chaque revue de code, à chaque nouvelle dépendance — c'est ce qui sépare un système qui dure d'un système qui s'effondre sous son propre poids.
~1 900 mots · 2024-03-15