3 techniques simples pour faciliter la compréhension d’une base de code
Nous développeurs avons tous connu ce phénomène : prendre un nouveau poste dans une entreprise et mettre du temps, des semaines voire des mois, pour atteindre une vélocité correcte. C’est normal, il faut apprendre et digérer la nouveauté, une nouvelle organisation, des éléments « métiers », d’éventuels nouveaux frameworks et bien sûr une nouvelle base de code.
La courbe d’apprentissage est normale mais peut-on agir sur cette durée ? Peut-on pallier avec des moyens simples cet inconvénient qu’est la non-connaissance de l’historique du produit et du projet ?
Au départ il y a la qualité intrinsèque du code. La courbe d’apprentissage est grandement améliorée lorsque l’on utilise les bonnes pratiques de développement :
- Les principes Clean Code
- Le DDD dans le sens où le « naming » business se retrouve dans le code
- Un code qui porte l’intention
- Un code structuré : le cerveau humain est une machine à repérer les structures
Not just what will the computer do with this code, but How can I communicate what I am thinking to people?
Kent Beck
Mais au-delà de ces bonnes pratiques quelles techniques peut-on utiliser ?
Technique 1 : créer de l’information au fur et à mesure
Première technique simple : bien utiliser ses outils
Un développeur passe beaucoup plus de temps à lire du code qu’à en écrire. En effet, il doit comprendre l’existant, l’analyser afin de faire évoluer le code. Souvent le code en lui-même ne suffit pas à savoir le pourquoi d’une architecture, d’une modélisation ou même d’une implémentation. Avoir des informations sur les choix passés est primordial. Pour cela, il faut créer cette information à chaque développement :
- Avoir un fichier readme.md pour le projet sur le repository Git donnant les informations essentielles (Exemple : informations de setup, résumé technique et fonctionnel du projet, etc.)
- Maintenir un fichier ADR sur le repository Git regroupant toutes les informations sur les choix d’architecture et de conception du produit (exemple : expliquer une dette technique choisie)
- Bien utiliser Git ou autre système de gestion de source, en veillant à bien :
- découper les commits et écrire ses messages de commits en expliquant la modification du code
- nommer la pull request en donnant l’information du changement sous un angle technique
- rédiger la user story associée, sous un angle fonctionnel
- lier la pull request à la user story
Plus le projet est ancien et plus le nombre d’intervenants a été élevé, plus ces points sont utiles.
Peu importent les outils, il est nécessaire de pouvoir retrouver ces informations. Dans notre entreprise nous utilisons la stack Microsoft, notre « tooling » tourne donc autour de Visual Studio et Azure Devops.
Concrètement, si un développeur souhaite en savoir plus sur un bloc de code ambigu, il peut retrouver des informations sur le besoin en quelques secondes pour comprendre le contexte du développement.
- On sélectionne le fichier et on regarde l’historique Git dans l’IDE
2. On retrouve la Pull request et on regarde toutes les modifications correspondant à ce développement
3. On retrouve la story avec le besoin et les éventuels critères d’acceptance
Technique 2 : ajouter de l’information business dans le code
Deuxième technique simple : Utiliser les attributs
Comme expliqué précédemment, le nommage « business » doit se retrouver dans le code. C’est la notion d’ « Ubiquitous Language » chère au DDD.
Or, ce nommage aussi utile soit-il répond seulement à 2 questions : le quoi et le comment. Un développeur nouvellement arrivé dans une équipe aura aussi besoin du pourquoi.
C’est la que la notion d’attribut de .NET est intéressante. Nous utilisons cette technique pour « augmenter » notre code afin d’ajout de l’information.
Ces informations peuvent également être extraites par code pour générer une documentation mise à jour automatiquement à chaque release, mais nous verrons cela plus en détails dans un prochain article :-)
Les attributs peuvent également être utilisés pour ajouter de l’information technique au développeur. Exemples :
- [Obsolete]
- [SideEffect]
- [DomainService]
- [LegacyImplementation]
- [Adapter]
- [CoreConcept]
Cela peut parfois remplacer avantageusement de nombreuses informations que l’on retrouverait dans un document distinct listant les guidelines de code de l’équipe.
On peut aussi aller beaucoup plus loin : Google Annotations Gallery : pour les intrépides, à ne pas mettre entre tous les mains :-)
Les attributs peuvent également nous être utiles dans certains cas où ils peuvent favoriser l’orthogonalité : changer B ne doit pas changer A.
Le manque d’orthogonalité est une grande source de bugs, en particulier pour les développeurs qui connaissent peu la base de code.
Exemple :
Sur notre projet nous avons une application backend et un frontend. Une page de notre frontend affiche des informations provenant du backend par extraction automatique. En revanche toutes les informations extraites ne sont pas utiles dans ce contexte d’affichage. Il faut donc effectuer une projection des données. Plutôt que de dupliquer de l’information en créant un fichier de configuration ou un mapping des données à afficher nous préférons ajouter un attribut explicite aux endroits souhaités côté backend :
[DisplayOnDocumentation]
La dépendance à une autre partie de la codebase est explicite. Le risque d’insérer un bug est grandement réduit.
Technique 3 : forcer le design
Troisième technique simple : abuser des tests unitaires
Comment forcer le design pour que la modification d’une dépendance ne soit pas oubliée ?
Les tests unitaires peuvent nous aider : exemples en C# avec NUnit et NSubstitute et FluentAssertions
Exemple 1
Dans notre entreprise, un de nos produits est une Api REST connectée à la base de données NoSql Azure CosmosDb :
- Un adapter se charge de la connexion à Azure
- Des énumérations C# contiennent la liste des collections CosmosDb du container
- Dans un autre namespace, des classes appelées au premier lancement de l’API (après le déploiement) se chargent de vérifier la disponibilité des dépendances sur l’infrastructure et notamment les différentes collections CosmosDb
- Cette classe doit donc être à jour si des collections sont ajoutées/renommées ou supprimées
2. Un test unitaire chargé uniquement de valider si la méthode de vérification des collections CosmosDb est bien appelée.
Exemple 2
Comme expliqué précédemment, nous utilisons les attributs pour donner de l’information au développeur.
Dans notre base de code, un ensemble de classes doivent systématiquement porter certains attributs. Utiliser la réflexion dans les tests unitaires n’est pas un problème si le test est toujours fiable et rapide : un test unitaire permet de garantir la présence d’attributs sur certaines classes ciblées.