Introduction
Les quatre piliers d’un logiciel
Concevoir un logiciel ne consiste pas uniquement à écrire du code qui fonctionne à un instant donné. Un logiciel est destiné à évoluer et à être maintenu pendant plusieurs années. Sa qualité peut notamment être évaluée au travers de quatre piliers :
- La performance, qui 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é, qui 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é, qui correspond à la facilité avec laquelle le code peut être compris, corrigé, testé et modifié.
- La robustesse, qui 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 est 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.
A qui s'adresse ce livre ?
Ce livre s’adresse aux développeurs qui possèdent déjà une première expérience en conception et programmation orientée objet avec la plateforme .NET et le langage C#. Il ne constitue pas une initiation au développement ni au langage C#, mais propose d’approfondir les principes et les pratiques permettant de concevoir des logiciels plus évolutifs, maintenables et robustes.
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 Design 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 et ne dispense pas de comprendre les principes qui permettent de construire un logiciel correctement structuré, performant, évolutif, maintenable et robuste.
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. Ils doivent 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.
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 les concepteurs et les développeurs à 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 Design Patterns de création tels que Factory Method, Abstract Factory, Builder ou Prototype permettent de mieux maîtriser la création des objets. Les Design Patterns structurels tels que Adapter, Decorator, Facade ou Proxy permettent d’organiser les relations entre les composants. Enfin, les Design Patterns comportementaux tels que Strategy, Observer, Command, State ou Mediator permettent d’organiser leurs interactions et leurs comportements.
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
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 systématiquement à tous les logiciels. Les principes SOLID permettent de mieux définir les responsabilités des classes et des composants, ainsi que leurs dépendance. Les Design Patterns proposent, quant à eux, des solutions éprouvées pour organiser ces responsabilités et ces dépendances dans des situations récurrentes. Je souhaite avant tout présenter les problématiques auxquelles ils répondent, expliquer pourquoi certaines conceptions deviennent progressivement difficiles à maintenir, et montrer comment ces principes et ces solutions 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. Ce livre est 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 refactorisations 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 ce livre peut vous aider à porter un regard différent sur votre code, à mieux comprendre certaines décisions de conception et, surtout, à construire des logiciels performants, évolutifs, maintenables et robustes, alors il aura atteint son objectif.