
Partager:
Max est un défenseur des développeurs Python et un ingénieur logiciel qui s'intéresse aux API de communication, à l'apprentissage automatique, à l'expérience des développeurs et à la danse ! Il a suivi une formation en physique, mais il travaille désormais sur des projets open-source et fabrique des objets qui améliorent la vie des développeurs.
Améliorez votre projet logiciel - Deuxième partie : Apporter des modifications
Temps de lecture : 16 minutes
Avez-vous déjà pris en charge une base de code et réalisé que vous n'étiez pas satisfait de la façon dont le code était écrit ou organisé ? C'est une histoire courante, mais qui peut causer bien des maux de tête. La dette technique peut faire boule de neige, rendant la compréhension du code et l'ajout de nouvelles fonctionnalités exponentiellement plus difficiles.
Dans cette série en trois parties, je vais passer en revue certaines des choses clés que vous voudrez faire pour devenir plus heureux avec votre (ancien) projet brillant. Pour donner des exemples concrets, j'expliquerai comment j'ai remanié et amélioré le logiciel libre Vonage Python SDKune bibliothèque qui fait des appels HTTP aux API de Vonage, mais les principes s'appliquent à n'importe quel type de projet logiciel.
Les exemples de ce billet seront écrits en Python, mais ces principes s'appliquent à des projets dans n'importe quel langage. Il y a également une liste de contrôle pratique à suivre si vous essayez spécifiquement de corriger un projet Python.
La série, en sections
Deuxième partie : apporter des changements (cet article)
Que couvre la deuxième partie ?
Dans la deuxième partie, nous aborderons les sujets suivants :
Devenir confiant pour apporter des changements et remédier à la dette technique
Instaurer la confiance avec votre patron, votre équipe et les utilisateurs
À la fin de l'article, vous serez prêt à changer les choses pour le mieux dans votre projet tout en satisfaisant les personnes concernées.
C'est parti !
Fixation de la structure
Si vous avez suivi première partie de cette série, vous avez déjà une bonne compréhension du projet dont vous êtes maintenant propriétaire, et vous savez comment la base de code (et les tests, si vous avez la chance d'en avoir) est structurée, et à quoi sert cette structure. C'est le moment idéal pour réfléchir à l'architecture globale et commencer à restructurer le projet de manière plus logique si vous pensez qu'il peut être amélioré.
Un exemple concret
Le projet que j'utilise comme exemple est le SDK Python de Vonagede Vonage, qui permet à un utilisateur d'appeler de nombreuses API de Vonage. Dans mon cas, la macrostructure de mon projet ressemblait à ceci :

Vous pouvez voir sur la figure ci-dessus que le code a été divisé en six modules principaux (des fichiers uniques, oui Python est très compact !):
Un module
__init__un module, typiquement un fichier minimal utilisé pour empaqueter le codeUn
_internalqui contient des méthodes internes (et également l'une des API que nous prenons en charge, pour une raison quelconque)sms,voiceetverifydes modules contenant chacun du code permettant d'appeler une seule APIUn module
errorsqui contient toutes les erreurs personnalisées
Ma première question en regardant cela a été : où sont les autres API ? Lorsque je suis arrivé chez Vonage, le SDK prenait en charge 12 API différentes (nous en avons ajouté d'autres depuis !), mais il n'y avait que 3 modules liés à ces API.
Il s'est avéré qu'environ 90 % du code se trouvait en fait dans le fichier __init__.py comprenant des implémentations de nombreuses API, des méthodes obsolètes, la logique pour des tâches telles que la génération de JWT, les requêtes API de base et d'autres paramètres optionnels... beaucoup trop pour un fichier qui devrait être très minimal. J'ai décidé de le refactoriser en créant une classe Client qui s'occuperait de la logique de base, puis des classes nommées d'après les API (avec un peu de regroupement logique).
Nous avons fini par obtenir une structure qui ressemblait à celle-ci :

Le faire soi-même
Examinez la structure de votre projet. Posez-vous la question suivante : pourrais-je l'améliorer ? Regroupez les fonctionnalités similaires en modules suffisamment petits pour être facilement compris, mais pas plus petits - vous voulez toujours pouvoir avoir une vue d'ensemble de ce que fait le projet.
Je recommande également de créer des classes pour contenir des méthodes similaires, ainsi que des erreurs personnalisées pour chaque classe afin de vous donner, ainsi qu'à vos utilisateurs, plus de visibilité sur ce qui ne va pas lorsque les choses se cassent.
Branches
Lorsque vous commencez un nouveau projet, il est probable qu'il n'ait pas été laissé dans un état "achevé". Les projets logiciels sont toujours un travail en cours et les améliorations itératives constantes sont une grande partie de la raison pour laquelle nous utilisons le contrôle de version. Dans ce cas, vous constaterez peut-être qu'il existe des branches dans votre système de contrôle de version qui contiennent des fonctionnalités, des améliorations ou des corrections de bogues.
Dans de nombreux cas, la situation idéale se présente comme suit : une branche principale unique, avec des fonctionnalités développées sur des branches secondaires, puis fusionnées dans la branche principale.

Dans certains projets, il peut être judicieux d'avoir une branche bêta plus longue, s'il y a une fonctionnalité bêta que vous voulez tester et garder séparée de votre base de code principale. Dans le SDK Python de Vonage, nous avons actuellement une branche bêta avec du code qui appelle notre Video API de Vonage, qui est en version bêta. Nous ne voulons pas fusionner cette branche car l'API qu'elle appelle n'est pas officiellement publiée. Dans ce cas, la structure de votre branche pourrait ressembler à ceci :

Dans le cas où vous devez développer des fonctionnalités séparément, il peut être judicieux de rebaser périodiquement la branche bêta pour intégrer les changements de la branche principale - c'est ce que je fais avec le SDK Python de Vonage.
Ce sont les "bons" cas. Mais lorsque vous arrivez à un projet, cela peut ressembler à ceci :

C'est-à-dire qu'il y a beaucoup trop de branches et un manque de cohérence. En général, les choses se terminent ainsi lorsque plusieurs développeurs travaillent simultanément sur de nombreuses fonctionnalités, mais il se peut aussi qu'un ingénieur précédent ait utilisé de nouvelles branches pour planifier diverses tâches et essayer de nouvelles choses, mais qu'il n'ait jamais eu le temps de les terminer.
Dans ce cas, ces branches peuvent être une excellente source d'informations sur la manière dont le précédent propriétaire du projet gérait le projet, ainsi qu'une source d'inspiration - certaines des fonctionnalités ou des corrections peuvent être suffisamment bonnes pour être mises en œuvre par vous-même. Cependant, une fois que vous avez compris ce que contient une branche supplémentaire, il est généralement préférable de la fermer - l'objectif est de vous donner autant que possible une ardoise vierge pour mettre en œuvre vos idées.
Clôture de Chesterton - Comprenez ce que vous enlevez !
Le moment semble bien choisi pour rappeler à tous (y compris à moi-même) l'existence de la la clôture de Chesterton - l'aphorisme qui dit qu'il ne faut pas enlever quelque chose sans comprendre pourquoi il a été mis là en premier lieu. Faites un effort pour comprendre le but d'une branche ou d'une partie du code avant de la supprimer ! Plus important encore : ne supprimez pas quelque chose simplement parce que vous ne savez pas pourquoi il est là, essayez d'abord de comprendre.
Choisir les bonnes dépendances
Presque tous les projets logiciels dépendent du code d'autres personnes. La beauté de l'open source réside dans le fait que de nombreux problèmes et défis ont été résolus avant que votre code n'apparaisse, par des personnes très intelligentes qui sont prêtes à partager leur travail avec vous. Cependant, il est important de comprendre les dépendances de votre code, leur utilité et s'il s'agit des meilleurs outils pour ce travail.
Une dépendance qui était appropriée dans le passé peut également devenir moins appropriée au fil du temps, ce qui nous amène à poser la question suivante :
Qu'est-ce qu'une bonne dépendance ?
Lorsque vous choisissez de conserver (ou de remplacer) une dépendance, cette section devrait vous aider à évaluer si une dépendance vaut la peine d'être utilisée.
Tout d'abord, prenez en compte la licence. Assurez-vous que vous êtes autorisé à utiliser la dépendance pour votre projet. Si vous ne le pouvez pas, c'est non. Si vous n'êtes pas sûr de ce dont je parle quand je dis cela, cet article de Snyk devrait vous aider à comprendre ce qu'il faut rechercher lors de l'évaluation de la licence d'une dépendance.
Ensuite, la dépendance est-elle activement maintenue ? Vous pouvez consulter l'historique des livraisons des projets open-source. Certains sont si simples qu'ils ne nécessitent que très peu de maintenance, et c'est très bien ainsi. Les projets plus complexes doivent être activement maintenus, c'est-à-dire qu'il doit y avoir des commits fréquents, et que les problèmes et les PRs ne sont pas ignorés pendant longtemps. C'est important car vous voulez que la dépendance prenne en charge les nouvelles versions de la langue et qu'elle soit compatible avec les nouvelles fonctionnalités et les autres dépendances que vous utilisez.
Il est utile de prendre en compte la popularité de la dépendance. Toutes les dépendances peuvent avoir des vulnérabilités de sécurité, et une dépendance plus largement utilisée est plus susceptible d'avoir ces vulnérabilités signalées par des outils automatisés, des chercheurs en sécurité et d'autres utilisateurs, ce qui augmente la probabilité que les vulnérabilités soient rapidement corrigées, assurant ainsi la sécurité de vos utilisateurs. Le choix d'une option largement utilisée signifie également qu'un plus grand nombre de personnes seront en mesure de répondre aux questions si vous êtes bloqué !
Enfin, considérez les ressources de soutien - la documentation est-elle claire ? Existe-t-il des exemples fiables en ligne qui utilisent cette dépendance ? Ces éléments peuvent vous aider à démarrer ou à déboguer les problèmes rapidement, ils valent donc la peine d'être pris en compte.
Vous avez trouvé l'ensemble parfait de dépendances ? Bonne nouvelle... pour l'instant. Une bibliothèque bien maintenue qui répond à vos besoins pourrait ne pas le rester - les mainteneurs pourraient abandonner le projet ou vos besoins pourraient changer, ce qui la rendrait inadaptée. Le meilleur conseil que je puisse vous donner est le suivant : continuez à qualifier vos dépendances au fil du temps ! Assurez-vous qu'elles répondent toujours à vos besoins. Et vous ne savez jamais, une nouvelle bibliothèque pourrait apparaître et répondre encore plus parfaitement à vos besoins à l'avenir.
Tester un code qui ne vous appartient pas
Si votre projet doit communiquer avec d'autres codes, l'épineux problème du test de la fonctionnalité de votre code se pose. Dans notre exemple, la tâche principale du SDK Python de Vonage est d'appeler de nombreuses API différentes. Nous n'avons aucun contrôle sur les API elles-mêmes, ni sur leur comportement.
Par exemple, lorsque vous utilisez le SDK pour appeler l'API SMS de l'API SMS de Vonage. De nombreuses choses peuvent se produire ici, en fonction de vos besoins exacts, mais je vais vous donner un exemple précis. L'envoi d'un SMS avec le SDK Python est un processus assez simple :
import vonage
client = vonage.Client(key="API_KEY", secret="API_SECRET")
client.sms.send_message(
{
"from": "SENDER_NAME"
"to": "RECIPIENT_PHONE_NUMBER",
"text": "A text message sent using the Vonage SMS API",
}
)Mais après avoir exécuté ce code, il se passe beaucoup de choses. Voici une version (très) simplifiée :
Le SDK envoie une demande à un serveur Vonage
Le serveur analyse la demande et renvoie une réponse au SDK.
Si la demande est correcte, le serveur envoie un SMS au numéro de téléphone du destinataire.
Les informations d'état (par exemple, le reçu de livraison) sont renvoyées au serveur Vonage.
En option, ces informations sont envoyées par un serveur Vonage à un webhook spécifié par l'utilisateur afin qu'il puisse voir si le SMS a été délivré avec succès.

Dans cette situation, l'exécution de notre code entraîne l'exécution de tout un tas d'autres codes. Nous ne pouvons pas tester cet autre code à partir du SDK, et nous devons donc faire une hypothèse lorsque nous écrivons nos tests : si nous envoyons les bonnes informations, nous ferons en sorte que les bonnes choses se produisent et nous obtiendrons les bonnes réponses en retour.
Dans notre exemple, la seule partie du code que nous pouvons tester est celle-ci :

Les seules questions sur lesquelles notre code peut être évalué sont donc les suivantes : 1. les requêtes que nous envoyons sont-elles bien formées, et 2. les réponses sont-elles traitées de manière appropriée ?
En résumé, limitez vos tests à la manière dont votre code envoie et reçoit des données, et supposez que l'autre logiciel fait son travail correctement, en envoyant des réponses de succès et d'erreur que votre code doit traiter. Lors des tests, envisagez de capturer les demandes d'API et de renvoyer des réponses fictives sous la forme appropriée à la demande, de sorte que votre code puisse consommer et traiter ces données fictives lorsque vous exécutez vos tests sans appeler réellement les services externes que vous ne pouvez pas contrôler.
Rappelez-vous que vos tests prouvent que votre code est conforme aux spécifications de votre suite de tests, et pas nécessairement au comportement réel de l'API. Lorsque vous ajoutez une nouvelle fonctionnalité, c'est une bonne idée de créer une application de démonstration qui utilise réellement votre code pour effectuer des requêtes en direct, en plus d'exécuter vos tests (cette approche vous fournit également une excellente réponse à mettre en cache pour l'utiliser avec vos tests à l'avenir !)
Faire sa première publication
Vous avez donc nettoyé la base de code, effectué quelques restructurations, amélioré vos tests et peut-être même ajouté de nouvelles fonctionnalités à votre projet. Il est temps de faire une nouvelle version !
Ce processus vise à instaurer un climat de confiance, à faire savoir à vos utilisateurs et à votre équipe que vous savez ce que vous faites. L'objectif est de montrer à vos utilisateurs qu'ils peuvent faire confiance à vos changements et que la mise à jour n'interrompra aucune partie de leur flux de travail sans qu'ils le sachent. La transparence est essentielle à cet égard : vous ne voulez pas réserver de surprises à vos utilisateurs.
Utiliser des versions sémantiques
La recommandation la plus importante est la suivante : utiliser version sémantique! Avec la version sémantique, vous suivez une structure x.y.z pour vos numéros de version, où :
x est la version majeure, que vous devez augmenter lorsque vous effectuez des modifications importantes,
y est la version mineure, que vous devez augmenter lorsque vous ajoutez des fonctionnalités rétrocompatibles, et
z est la version du correctif, que vous devez utiliser lorsque vous corrigez un bogue, mettez à jour une dépendance, etc. de manière à assurer la compatibilité avec le passé.
Pour donner un exemple concret, lors de l'ajout de la prise en charge de l'API Vonage Messages API (mais sans rien changer qui rendrait la nouvelle version incompatible avec le passé), j'ai fait une version mineure, de v2.7.0 -> v2.8.0. Lorsque j'ai voulu supprimer certaines méthodes obsolètes et modifier la façon dont les objets sont instanciés dans le SDK, j'ai su que cela briserait des fonctionnalités existantes, et j'ai donc créé une version majeure, v2.8.0 -> v3.0.0. Lorsque j'ai trouvé un bogue dans le nouveau code, je l'ai corrigé et j'ai mis à jour le SDK avec une version corrective, v3.0.0 -> v3.0.1. En suivant ces règles, l'utilisateur sait exactement à quoi s'attendre lorsqu'il passe à la dernière version. Si vous faites une version majeure, ils savent qu'ils doivent s'attendre à un changement radical !
Mise à jour des documents complémentaires
Chaque fois que vous publiez une nouvelle version, mettez à jour le journal des modifications, afin que l'utilisateur sache à quoi s'attendre. C'est souvent le premier endroit que l'utilisateur consulte, car il explique toutes les différences de la nouvelle version et peut l'aider à décider s'il doit la mettre à jour. Si vous ne mettez pas à jour le journal des modifications, les utilisateurs ne sauront pas à quoi s'attendre d'une nouvelle version et seront donc beaucoup plus méfiants.
De la même manière, lorsque vous publiez une nouvelle version, veillez à ce que la documentation, les README et les exemples de code soient mis à jour avec la dernière version. Si vous ne le faites pas, vos utilisateurs ne sauront pas nécessairement comment utiliser toutes vos nouvelles fonctionnalités, et si vous avez effectué un changement radical, votre documentation et vos exemples de code risquent de ne plus fonctionner du tout.
N'oubliez pas que la transparence est essentielle - soyez prévisible, afin que les utilisateurs de longue date du projet puissent être sûrs que le code sur lequel ils comptent est entre de bonnes mains, et que les nouveaux utilisateurs puissent apprendre à se servir de votre logiciel.
Assouplissement des dépréciations
Lorsque vous commencez à modifier du code, vous voudrez probablement en restructurer/refactoriser une grande partie. Tant que cela ne change pas la façon dont le code est appelé par un utilisateur, vous pouvez le faire à votre guise. Cependant, si vous voulez changer la façon dont quelque chose est appelé, vous devrez déprécier l'ancienne méthode et ajouter la nouvelle dans une version mineure, puis supprimer l'ancienne dans une version majeure plus tard.
Je vais vous donner un exemple précis. Dans le SDK Python de Vonage, je voulais changer la façon dont la méthode get_standard_number_insight était appelée par un utilisateur. À l'origine, il s'agissait d'une méthode associée à la classe Client à l'origine. Je voulais changer la structure en ayant une NumberInsight contenant cette méthode, que la classe Client instancie et utilise. Cela rendrait cette façon d'appeler l Number Insight API Number Insight de la même manière qu'un utilisateur envoie des SMS ou passe des appels vocaux.

Tout d'abord, j'ai créé une classe NumberInsight et j'y ai ajouté une version de la méthode get_standard_number_insight à cette classe.

Ensuite, j'ai déprécié la méthode dans la classe Client . En Python, cela peut être fait en ajoutant un décorateur qui imprime un avertissement de dépréciation à l'utilisateur lorsqu'il utilise la méthode.

Ensuite, j'ai mis à jour les extraits de code et la documentation pour refléter le changement.

J'ai maintenant pu faire une version mineure. Après une version où vous dépréciez une fonctionnalité, c'est une bonne pratique de laisser les parties dépréciées tranquilles pendant un certain temps avant de les supprimer, afin de laisser suffisamment de temps aux gens pour passer à autre chose. Je suggère de laisser la fonctionnalité obsolète pendant au moins une version ou un mois, selon ce qui est le plus long. Encore une fois, il s'agit d'instaurer la confiance.
J'ai laissé cet ancien code pendant quelques mois, puis j'ai publié une version majeure dans laquelle j'ai supprimé les anciennes façons d'appeler ces méthodes. En étant méthodique et transparent sur mes intentions, mes utilisateurs savaient à quoi s'attendre lorsqu'ils passaient à la dernière version.

Équilibrer les améliorations et les nouveaux travaux
La dernière chose à prendre en compte lorsque vous commencez à travailler sur une base de code ancienne est que vous ne disposerez probablement pas d'un temps infini pour peaufiner le code comme vous l'entendez avant de devoir ajouter de nouvelles fonctionnalités, corriger des bogues, etc. Vous devrez probablement jongler avec d'autres priorités, par exemple des délais pour ajouter de nouvelles fonctionnalités, tout en essayant d'apporter des changements importants à l'ancien code.
Dans ce cas, vous devez être à l'aise pour vous défendre et défendre votre travail. Il faut s'attendre à ce qu'une partie de votre temps soit consacrée à l'amélioration de la base de code existante, ce qui signifie que vous ne serez pas aussi prompt à développer de nouvelles fonctionnalités que votre patron le souhaiterait. Insistez sur le fait que le temps que vous passez à remanier maintenant vous aidera à comprendre la base de code et portera ses fruits plus tard, car elle sera beaucoup plus facile à maintenir.
En fin de compte, votre travail sur un projet existant devrait améliorer l'expérience de l'utilisateur et sa facilité d'utilisation et de maintenance à long terme. Il se peut que vous ne receviez pas d'éloges immédiats pour avoir traité la dette technique, car le travail n'est pas visible immédiatement pour vos utilisateurs ou votre équipe, mais la gestion de la dette technique peut empêcher que les choses soient bien pires pour tout le monde plus tard - ce travail vaut vraiment la peine d'être fait !
Un élément qui m'a beaucoup aidé dans ce processus a été la création de fiches de travail pour la découverte et l'apprentissage, afin de montrer que j'investissais dans l'efficacité future. J'ai également créé des tickets pour la dette technique, afin de donner à mon équipe un aperçu du travail que je faisais, parallèlement aux tickets pour le développement de nouvelles fonctionnalités. Cette approche m'a permis d'instaurer la confiance au sein de mon entreprise et de permettre aux gens de comprendre comment je passais mon temps, et je ne saurais trop la recommander.
Quelle est la prochaine étape ?
Si vous avez suivi les suggestions de cet article, votre projet aura déjà bien meilleure allure ! Les libérations que vous ferez jetteront les bases de toutes les grandes choses que vous voudrez faire avec votre projet.
Cliquez ici pour lire la troisième partie de la sérieoù j'expliquerai comment faire passer votre projet au niveau supérieur. Nous parlerons des améliorations qui profitent à vos utilisateurs, ainsi que des choses qui rendent la base de code plus facile à travailler. Enfin, nous expliquerons les meilleures pratiques pour transmettre un projet.
Vous avez une question ou souhaitez nous faire part de vos réflexions ? Vous pouvez nous contacter sur notre Communauté Vonage Slack ou nous envoyer un message sur sur Twitter.
Partager:
Max est un défenseur des développeurs Python et un ingénieur logiciel qui s'intéresse aux API de communication, à l'apprentissage automatique, à l'expérience des développeurs et à la danse ! Il a suivi une formation en physique, mais il travaille désormais sur des projets open-source et fabrique des objets qui améliorent la vie des développeurs.