Introduction
Les quatre piliers d’un logiciel
Concevoir un logiciel ne consiste donc pas uniquement à écrire du code qui fonctionne à un instant donné. Un logiciel est généralement destiné à vivre, à évoluer et à être maintenu pendant plusieurs années. Sa qualité peut notamment être évaluée au travers de quatre piliers :
- La performance désigne sa capacité à accomplir efficacement les traitements attendus, en maîtrisant notamment les temps d’exécution et les ressources consommées.
- L’évolutivité représente sa capacité à accueillir de nouvelles fonctionnalités ou à s’adapter à de nouveaux besoins sans nécessiter une remise en cause importante de sa conception ou de son architecture.
- La maintenabilité correspond à la facilité avec laquelle le code peut être compris, corrigé, testé et modifié.
- La robustesse caractérise sa capacité à conserver un fonctionnement maîtrisé face aux erreurs, aux situations inattendues et aux conditions particulières d’exécution.
Ces quatre piliers ne dépendent pas uniquement des technologies utilisées. Ils sont également fortement influencés par les choix de conception effectués au début du projet, puis tout au long de son développement.
Un framework récent, une architecture moderne ou un langage performant ne peuvent pas, à eux seuls, garantir la qualité d’un logiciel. La manière dont les responsabilités sont réparties, dont les composants communiquent entre eux et dont leurs dépendances sont organisées joue un rôle déterminant.
Le code écrit aujourd’hui sera à maintenir demain
Toute ligne de code écrite aujourd’hui devient potentiellement une ligne de code à comprendre, maintenir ou faire évoluer demain.
Cette maintenance pourra être réalisée quelques jours plus tard par son auteur, plusieurs mois plus tard par un autre développeur ou plusieurs années plus tard par une équipe qui ne connaît ni les choix initiaux ni le contexte dans lequel le logiciel a été développé.
Un code mal structuré peut parfaitement fonctionner aujourd’hui tout en constituant une source importante de difficultés demain. Une modification apparemment mineure peut nécessiter de comprendre de nombreuses dépendances, provoquer des effets de bord ou imposer des changements dans plusieurs composants qui auraient dû pouvoir évoluer indépendamment.
À l’inverse, un code dont les responsabilités sont clairement identifiées, dont les dépendances sont maîtrisées et dont la conception repose sur des principes connus facilite sa compréhension et son évolution.
La maintenabilité ne commence donc pas lorsque le logiciel entre en maintenance. Elle commence dès sa conception et dès les premières lignes de code. Investir du temps dans la conception n’a pas pour seul objectif d’obtenir un code plus élégant. Il s’agit surtout de maîtriser le coût, la complexité et les risques associés aux évolutions futures du logiciel.
Des solutions « maison » aux solutions éprouvées
Face à un problème de conception, il est toujours possible d’imaginer sa propre solution. Celle-ci peut parfaitement fonctionner et sembler simple au développeur qui l’a conçue. Cependant, une solution facilement compréhensible par son auteur ne l’est pas nécessairement pour les développeurs qui devront ensuite intervenir sur le logiciel.
Cette situation peut conduire à multiplier les mécanismes spécifiques à une application ou à une équipe. Chaque nouveau développeur doit alors comprendre non seulement le besoin fonctionnel, mais également les solutions particulières imaginées par ses prédécesseurs.
Or, de nombreux problèmes de conception auxquels nous sommes confrontés ne sont pas nouveaux. Ils ont déjà été étudiés et des solutions génériques, éprouvées et documentées existent.
Utiliser ces solutions permet également de disposer d’un vocabulaire commun entre concepteurs et développeurs. Indiquer qu’une partie d’une application repose sur Strategy, Factory Method, Decorator, Observer ou Mediator permet à une personne connaissant ces patterns d’en comprendre plus rapidement l’intention de conception, sans devoir commencer par analyser l’ensemble de son implémentation.
Il ne s’agit cependant pas d’appliquer systématiquement une solution existante dès qu’un problème apparaît. Une bonne conception n’est pas celle qui utilise le plus grand nombre de principes SOLID ou de Design Patterns. C’est celle qui répond au besoin tout en restant suffisamment simple, compréhensible et adaptable pour être maintenue dans le temps.
L’essentiel est donc de connaître ces solutions, de comprendre les problèmes auxquels elles répondent et de savoir déterminer dans quelles situations leur utilisation apporte une réelle valeur.
Concevoir des logiciels à l’ère de l’intelligence artificielle
Cette capacité de discernement me paraît d’autant plus importante que notre métier connaît aujourd’hui une nouvelle transformation majeure : l’arrivée de l’intelligence artificielle générative dans le développement logiciel.
Nous pouvons désormais générer en quelques secondes des classes, des méthodes, des tests unitaires, des algorithmes et parfois des parties importantes d’une application. Cette capacité transforme profondément notre manière de produire du code, mais elle ne supprime pas la nécessité de savoir concevoir un logiciel. Elle la renforce.
Un code syntaxiquement correct et fonctionnel ne garantit ni sa maintenabilité, ni son évolutivité, ni sa robustesse, ni sa cohérence avec l’architecture existante. Il appartient toujours au concepteur et au développeur de comprendre les solutions proposées, de les analyser, de les remettre en question et de déterminer comment les intégrer dans une architecture cohérente.
Le rôle du développeur ne peut donc pas se limiter à communiquer à un ordinateur les traitements qu’il doit exécuter au moyen d’un langage de programmation. Il doit également être concepteur: savoir répartir les responsabilités entre les différents composants, limiter leurs dépendances, anticiper les évolutions et choisir des solutions adaptées aux problèmes rencontrés.
L’intelligence artificielle peut assister la production du code. Elle ne dispense pas de comprendre les principes qui permettent de construire un logiciel correctement structuré.
Les principes SOLID et les Design Patterns
Les principes SOLID et les Design Patterns constituent deux outils importants pour répondre à ces problématiques.
Les principes SOLID fournissent un ensemble de recommandations permettant de guider la conception orientée objet. Ils encouragent notamment à mieux répartir les responsabilités, à limiter le couplage entre les composants, à favoriser les abstractions et à construire des éléments capables d’évoluer sans remettre en cause l’ensemble de l’application.
Les Design Patterns, quant à eux, proposent des solutions de conception réutilisables pour répondre à des problèmes récurrents. Ils ne constituent pas des morceaux de code prêts à être copiés dans une application. Ils décrivent plutôt une organisation des responsabilités et des interactions entre différents objets.
Les patterns de création tels que Factory Method, Abstract Factory, Builder ou Prototype permettent de mieux maîtriser la création des objets. Les patterns structurels tels que Adapter, Decorator, Facade ou Proxy permettent d’organiser les relations entre les composants. Enfin, les patterns comportementaux tels que Strategy, Observer, Command, State ou Mediator permettent d’organiser leurs interactions et leurs comportements.
Principes SOLID et Design Patterns sont étroitement liés. Les premiers fournissent des principes permettant de guider les décisions de conception, tandis que les seconds proposent des solutions éprouvées à certains problèmes de conception récurrents.
Ils ne doivent cependant pas être considérés comme des règles à appliquer mécaniquement. Leur intérêt réside avant tout dans la compréhension des problèmes auxquels ils répondent et dans la capacité du concepteur à déterminer quand leur utilisation est pertinente.
L’objectif de ce livre
C’est dans cet esprit que j’ai souhaité écrire ce livre.
Mon objectif n’est pas de présenter les principes SOLID et les Design Patterns comme un ensemble de recettes qu’il suffirait d’appliquer à chaque application. Je souhaite avant tout expliquer les problématiques auxquelles ils répondent, comprendre pourquoi certaines conceptions deviennent progressivement difficiles à maintenir et montrer comment différentes pratiques peuvent contribuer à les améliorer.
Les exemples proposés s’appuient sur la plateforme .NET et le langage C#, qui m’accompagnent depuis plus de deux décennies. Au travers d’explications et d’exemples concrets, nous verrons non seulement comment mettre en œuvre ces principes et ces patterns, mais surtout pourquoi et dans quelles situations les utiliser.
Ce livre est également le résultat de nombreuses années consacrées à concevoir, développer, maintenir, expliquer et transmettre. Il rassemble des pratiques que j’utilise dans mes projets, mais aussi des enseignements tirés de situations rencontrées au cours de ma carrière.
Car la conception logicielle ne s’apprend pas uniquement dans les livres. Elle se construit également au fil des projets, des erreurs, des évolutions, des refactorings et des échanges avec les autres concepteurs et développeurs.
La qualité d’une conception ne se mesure finalement pas uniquement à la manière dont un logiciel fonctionne aujourd’hui, mais également à la facilité avec laquelle il pourra être compris, maintenu et adapté aux besoins de demain.
Si cet ouvrage peut vous aider à porter un regard différent sur votre code, à mieux comprendre certaines décisions de conception et, surtout, à construire des logiciels capables de continuer à évoluer pendant plusieurs années, alors il aura atteint son objectif.