J’ai mal à mon application ! Ca se soigne ?
Dans ce billet, nous nous posons une question simple : comment construire de meilleures applications ? Il s’agit ici de prendre un peu de recul sur notre manière de concevoir du software, d’essayer d’identifier les vrais objectifs d’une application et les principes majeurs qui devraient nous guider dans sa construction. Dans des articles à venir, nous présenterons certaines manières d’approcher le design logiciel qui peuvent nous aider à atteindre ces objectifs.
Une application qui fait du bien, c’est quoi ?
Un après-midi, autour d’un verre et encore sous les effets béatifiants d’une session jazz dans un parc parisien, nous commencions par nous poser la question existentielle suivante : c’est quoi une bonne application ?
Nous pensions alors à une application facile à prendre en main, simple d’utilisation au quotidien, efficiente, qui s’adapte à nos habitudes et à nos préférences... mais aussi, vue des équipes chargées de sa réalisation et de son exploitation, aisément maintenable, adaptable, extensible, scalable, etc.
Finalement, une bonne application m’aide à accomplir les tâches que je souhaite réaliser.
Aujourd’hui, cet objectif est-il atteint ? Ne généralisons pas… Mais en observant les plateaux projets, on se rend fréquemment compte que tous les acteurs impliqués dans la production et l’utilisation de logiciels sont insatisfaits. Bien sûr, le degré d’insatisfaction (et les envies associées) varient selon les contextes et les rôles.
D’une manière générale, nous sommes insatisfaits lorsque :
- Nous ne parvenons pas à exploiter les éléments (du code, des logs, une documentation, une interface utilisateur) dont nous avons besoin pour travailler, car nous ne les comprenons pas
- Nous n’arrivons pas à atteindre nos objectifs de performance, à être efficients avec les outils mis à notre disposition
Des contradictions à résoudre
Ces insatisfactions peuvent s’expliquer en partie par les contradictions suivantes :
- Plusieurs acteurs sont parties prenantes dans la construction d’une application, avec des objectifs potentiellement divergents (simplicité du code, simplicité de l’interface utilisateur, réutilisabilité…)
- Des individus aux compétences et aux angles de vue différents sont mis à contribution pour construire un produit cohérent
- Le produit développé doit apporter de la valeur dès aujourd’hui tout en s’inscrivant dans une démarche d’investissement et de durabilité
La plupart du temps, nous sommes conscients de ces contradictions, nous tentons de satisfaire chacune des parties. Pourtant, nous faisons systématiquement des compromis, souvent à la faveur de l’une d’entre elle ...
Les anti-patterns récurrents
Ces contradictions peuvent mener à l’apparition de l’un ou plusieurs des anti-patterns suivants :
CRUD UI
Les interfaces utilisateurs sont orientées données. Elles ne guident pas l’utilisateur dans ses activités, dans l’accomplissement de ses tâches ; ce sont de simples outils graphiques permettant de consulter ou mettre à jour des données. Il en résulte des applications peu intuitives et peu ergonomiques. Attention, une IHM CRUD n’est pas un anti-pattern dans tous les cas (applications de type gestion de référentiel, utilisateurs avec des profils techniques, etc.)
Tour de Babel
Pour désigner le concept fonctionnel “Dossier de subvention”, Bob le codeur dit “Folder”, Michel le fonctionnel dit “Dossier”, et Henri l’utilisateur dit “Subvention”. Les conséquences sont évidemment que personne ne se comprend ; au mieux, on a du mal à accomplir son travail efficacement ; au pire, on fait des erreurs d’interprétation et on prend de mauvaises décisions. Ce problème se pose donc aujourd’hui sur la communication entre les différents acteurs ; mais également à travers le temps (comprendrons-nous demain ce que nous comprenons aujourd’hui ?)
Big Design Up Front
- “Cette fonctionnalité, elle sera peut-être nécessaire dans d’autres applications. Autant en faire un JAR technique transverse ou un service publié sur ESB”
- “Cette fois, on ne va pas laisser les développeurs faire n’importe quoi : la règle c’est que chaque classe du modèle doit hériter d’un classe abstraite et doit avoir une factory, et un DAO.”
On part d’une bonne intention : celle de faire bien, propre, évolutif, réutilisable, extensible... Finalement, on s’aperçoit qu’on a fait des choix structurants beaucoup trop tôt, qu’on a ajouté de la complexité inutilement (complexité accidentelle) et qu’on a bridé la liberté du développeur (nécessaire pour attaquer des problématiques spécifiques qui, elles, vont vraiment apparaître) et les possibilités de prise en compte du changement, à l’inverse de notre intention initiale.
One Size Fits All
On essaye de faire une application qui fait tout alors qu’il faudrait peut être en faire plusieurs ; on utilise la même stack technique partout alors qu’elle ne répond peut-être pas aux enjeux poursuivis dans ce cas précis, le même modèle dans des contextes qui n'adressent pas les mêmes problématiques… Notre volonté de réduire les coûts “à tout prix”, de rationaliser, de mutualiser, d’appliquer des recettes éprouvées peut nous conduire à mettre en place des solutions non optimales.
Big Ball Of Mud
On a des langages objet, des mécanismes de découplage et d’abstraction, de l’inversion de contrôle et de l’injection de dépendances, des couches aux responsabilités bien définies, et pourtant… non. On a toujours le sentiment que ce bout de code n'a rien à faire ici ; que cette règle de gestion est éclatée ou dupliquée un peu partout dans le code ; que ces composants sont finalement bien dépendants les uns des autres... Comme si malgré tous les efforts du monde, il fallait quand même qu’on se retrouve avec ce satané plat de spaghettis en guise de dessert.
Quelques pistes....
OK, alors quelles sont les vraies solutions ?
Évidemment, pas de silver bullet... Et évidemment, tout n’est pas question d’outil ou de technologie. Néanmoins, n’y aurait-il pas quelques manières d’approcher le design de logiciels, quelques bonnes pratiques, qui pourraient nous mettre sur la bonne voie ?
L’utilisabilité
L'utilisabilité désigne la qualité de l'expérience utilisateur offerte par une application pour réaliser une tâche. L'objectif est de maximiser l'efficacité, l'efficience et la satisfaction de l'utilisateur. On est donc loin de simples préoccupations cosmétiques ! Un certain nombre de pratiques de conception d'IHM et de patterns, telles que les IHM orientées activités, sont abordés dans le livre blanc OCTO “Les dossiers de l’écran : utilisabilité et technologies IHM”.
Les pratiques Agiles
Il nous paraît indispensable de vite confronter nos développements à la réalité, et de se tromper rapidement (fail fast) pour apprendre de nos erreurs.
Nombre de pratiques agiles nous ont déjà convaincus et ont fait leurs preuves. Construire un produit de manière incrémentale, sur des cycles courts, en obtenant un feedback rapide et régulier du client. L’accent porté sur une vision “produit” (et pas uniquement “projet”), l’importance donnée à la qualité, ainsi que le rôle du Product Owner, garant de la cohérence globale du produit construit, sont essentiels à la création d’applications qui ont du sens.
Le test comme moyen de spécification permet d’avoir des éléments de documentation pour les équipes de développement ; il facilite l’appréhension du code (quelles sont les fonctionnalités offertes par ce code ?). En servant de protection contre la non-régression, il ouvre la voie au refactoring et donc à la prise en compte du changement, fonctionnel et technique.
Faire simple !
KISS, YAGNI sont des principes qui invitent à faire simple. Avant de vous attaquer à une complexité, attendez de la voir apparaître. Repoussez les limites (grâce au refactoring !) une fois que vous les avez atteintes, pas avant. En résumé, une invitation au pragmatisme qui semble s’accorder particulièrement bien avec l’Agile.
Des principes de design
Des pratiques de développement, une attention portée à l’utilisabilité, du pragmatisme... sont autant d’éléments qui peuvent nous guider dans l’atteinte de nos objectifs.
Et côté design logiciel ?
Certains principes, compatibles avec les idées exprimées jusqu’ici, nous semblent primordiaux pour arriver à construire de meilleures applications :
- Faciliter la communication entre acteurs projet via l’utilisation d’un langage commun - Retranscrire ce langage et les concepts métier associés dans le code - Bâtir des interfaces utilisateurs orientées tâches et activités - Expliciter l’intention et la propager dans le système : qu’est-ce que l’utilisateur veut faire ? Qu’est-ce que le développeur a voulu exprimer ? - Architecturer une application en identifiant les différentes responsabilités mais également les différentes contraintes : ne pas essayer d’appliquer les mêmes solutions pour résoudre des problèmes différents - Architecturer l’application de telle sorte qu’elle n’empêche pas le changement : adresser de nouvelles exigences fonctionnelles (nouveaux besoins métier), non-fonctionnelles (scalabilité, performances) et technologiques (multicanal, mode déconnecté,...).
Ce dernier point représente un véritable enjeu, car il est critique pour l'amélioration continue de nos applications : quelles pratiques et patterns de design pouvons nous mettre en place pour permettre, demain, le refactoring de notre code (et de notre architecture logicielle) à un coût acceptable, sans retomber dans les travers du Big Design Up Front et de la surconception ?
Dans un article à suivre, nous nous intéresserons à l’approche Domain Driven Design. Nous aborderons ses concepts structurants et les solutions qu'elle propose pour faciliter la communication sur le domaine fonctionnel, la cohérence dans le code, et l’ouverture de nos applications au changement.