IT & Engineering

Les bugs informatiques et comment les corriger plus rapidement

Le véritable coût d'un logiciel ne réside pas dans sa création, mais dans sa maintenance. Si cela est vrai, alors les stratégies que nous employons pour aider à réduire les coûts globaux résident dans la maintenance quotidienne du code. Vous ne nous croyez pas ? Poursuivre la lecture.
Image pour Les bugs informatiques et comment les corriger plus rapidement

Le coût du débogage n’est pas le même pour tout le monde. Ce coût ne dépend pas uniquement des frais d’exploitation et de service, mais aussi de votre dette technique.

Lorsque nous parlons de dette technique, nous faisons référence au coût supporté par les entreprises lorsqu’elles ne résolvent pas des problèmes qui les affecteront à l’avenir. Cela peut coûter cher, mais que se passerait-il s’il existait des modèles ou des stratégies que l’équipe de développement pouvait exploiter pour réduire le temps nécessaire à l’identification et à la résolution des bugs ?

Dans cet article, nous joignons le geste à la parole en partageant la stratégie que nous avons trouvée la plus efficace pour réduire le temps de résolution des bugs.

Qu’est-ce qu’un bug informatique ?

Un bug informatique est simplement une erreur de codage dans un programme. Les bugs sont assez fréquents dans un programme ou une application unique, mais si nous prenons du recul pour observer une plateforme entière de services intégrés et de code interconnecté, la perspective de la chasse aux bugs commence à ressembler à l’adage de l’aiguille dans une botte de foin.

Corriger un bug prend plus de temps que d’écrire une ligne de code. Il est donc logique d’investir dans votre développement logiciel dès le début. N’est-ce pas ?

Le prix de la réussite : les processus de développement logiciel

Se donner les moyens de réussir implique un coût initial élevé, tant en termes de temps que de ressources. Le développement de logiciels sur mesure peut avoir un prix moyen allant jusqu’à 250 000 $ selon votre infrastructure et vos besoins. Il est donc essentiel d’un point de vue financier de planifier votre stratégie de maintenance plutôt que de subir le coût de la création constante de nouveau code.

Modéliser votre environnement de production

Vous avez autant de chances de reproduire un bug dans un environnement local ou contrôlé qu’un rat d’y survivre dans une ferme à chats, à moins d’avoir modélisé votre environnement de production aussi fidèlement que possible.

Cette solution peut prendre plusieurs formes, notamment la pratique courante consistant à créer un environnement de préproduction ou de développement. Cependant, ces environnements coûtent cher, et ceux à longue durée de vie ont tendance à dériver avec le temps. Au cours des 20 années d’expérience de Derrick en tant que développeur, rien ne vaut la capacité à reproduire un problème sur votre machine locale. Cela signifie que vous devez avoir la capacité de modéliser l’environnement de production localement de la manière la plus réaliste et reproductible possible.

Les implémentations de cette méthode les plus réussies que nous ayons vues concernent les architectures qui utilisent des micro-services ou, comme nous aimons les appeler, des « services à portée de domaine ». Dans ces environnements, l’objectif est de mettre en place chaque service dépendant de façon à ce que le service sur lequel vous travaillez ne puisse pas faire la différence entre une exécution sur votre machine locale et une exécution en production.

Comment y parvenir ? Le plus simple est d’exiger des propriétaires de services qu’ils fournissent une implémentation simulée ou fictive facile à utiliser de l’interface publique de leur service. C’est le parfait exemple pour se donner les moyens de réussir. Créer ces simulations prend du temps et des ressources. Si votre culture de développement consiste à fournir ces outils, l’objectif de modéliser votre environnement de production dans le code devient beaucoup plus simple, car la majeure partie du travail fastidieux d’implémentation d’un service fictif ou simulé est faite pour vous. Dans le cas contraire, vous devrez en payer le prix en le créant vous-même.

Si votre organisation utilise Golang, vous pouvez importer des services simulés directement depuis le code du service dépendant, ce qui simplifie la mise à jour en cas de modifications.

Lors de la simulation de vos services, vous ne pouvez pas vous contenter de recréer les interfaces externes, vous devez également inclure les protocoles de transport. Vous vous demandez peut-être pourquoi ? Ne vaudrait-il pas mieux éviter d’effectuer de coûteux appels à distance ? Serait-ce moins cher ? Oui, mais ce ne serait pas efficace. Le but est de modéliser l’environnement de production aussi fidèlement que possible ; cela inclut le transport qu’utilise votre application pour communiquer avec les autres services. Qu’il s’agisse de HTTP, de GRPC ou d’un bon vieux TCP, des variations mineures dans la pile de transport ou les bibliothèques de marshalling peuvent avoir des répercussions très subtiles sur le fonctionnement de votre code en production.

Petite anecdote : comment nous avons appris cela chez Mailgun

Nous avions un service qui a changé de bibliothèques de marshalling JSON dans le but d’augmenter les performances, mais cela a introduit involontairement un problème d’analyse unicode. Simuler l’interface au lieu d’appeler réellement le transport et la fonction de marshalling nous aurait masqué ce problème.

Un autre problème inattendu impliquait une modification de la bibliothèque DNS Golang, qui aurait complètement brisé notre environnement de production sans notre suite fonctionnelle, et oui, nous exécutons une implémentation simulée du DNS pour nos tests.

Créer des tests fonctionnels pour trouver les cas extrêmes et diagnostiquer les problèmes

Bien, une fois notre environnement modélisé, la première chose à faire est de créer une suite de tests fonctionnels. Une suite de tests fonctionnels est un conteneur qui regroupe un ensemble de tests conçus pour faciliter l’exécution et le signalement de l’état d’exécution des tests. Vous pouvez ajouter des cas et des plans de test à vos suites pour couvrir une variété de scénarios et de cas particuliers. Plus votre suite sera de qualité, plus il sera facile de diagnostiquer un problème.

Si le problème que vous essayez de résoudre existe sous la forme d’un test fonctionnel, vous pouvez facilement (et rapidement) remplacer les valeurs du test par les valeurs exactes trouvées en production. Le simple fait de reproduire le problème localement en recréant les données exactes de la production peut mener à la solution. Si vous avez facilité le fait d’importer des données depuis la production ou de simuler des données de production, cela peut devenir un outil inestimable à votre disposition.

Si vous n’avez pas de test fonctionnel existant couvrant ce scénario (ce qui est fréquent, car votre base d’utilisateurs trouve souvent des façons agaçantes, euh… uniques et imprévues d’utiliser votre système), vous devrez le créer.

Screenshot_2023-04-20_at_18
Autoportrait d’une personne frustrée de l’équipe d’ingénierie qui n’a pas créé de tests fonctionnels.

Se donner les moyens de réussir implique en partie de mener des tests fonctionnels ou d’avoir la capacité de créer rapidement un test fonctionnel. Les tests unitaires (qui testent la plus petite partie de code pouvant être isolée logiquement dans un système) peuvent être utiles une fois que vous avez cerné le problème. Un test fonctionnel vous permet de brosser un tableau général de votre service et de comprendre, du point de vue du client, comment le service fonctionne dans un scénario particulier. Lors du débogage, il est toujours plus important de tester le produit que de tester le code.

Créez des tests fonctionnels automatisés au sein de vos suites, qui exécuteront automatiquement les cas de test. Utilisez cette méthode dans le cadre de votre programme d’assurance qualité (QA) pour découvrir et corriger les bugs avant la mise en production de votre application.

Pourquoi vous devriez éviter les tests manuels et ce qu’il faut faire à la place

À l’opposé des tests fonctionnels, on trouve les tests manuels. Les tests manuels sont un type de test logiciel dans lequel les cas de test sont exécutés manuellement par une personne chargée des tests, sans utiliser d’outils automatisés. Vous devriez toujours privilégier les tests fonctionnels par rapport aux tests manuels, car un test manuel est sujet aux erreurs et n’est pas reproductible.

De nombreuses personnes en programmation font l’erreur d’exécuter un service localement et de tester manuellement les points de terminaison pour diagnostiquer des problèmes. L’ayant fait pendant de nombreuses années, nous ne comptons plus le nombre de fois où nous avons reproduit un problème sans savoir exactement comment nous avions procédé, ce qui a provoqué des envies de tout envoyer balader en essayant de retracer nos pas.

Les sessions de débogage passent souvent de quelques heures à plusieurs jours, et avec les tests manuels, tous les scénarios que nous essayons ne sont pas documentés et finissent par se confondre. Cependant, si tous ces scénarios tentés sont rédigés sous forme de tests fonctionnels, chaque scénario peut devenir une partie de l’historique de la session de débogage.

Si vous écrivez des tests fonctionnels pour de nouveaux scénarios, ils peuvent être intégrés à la suite de tests de régression exécutée pendant l’intégration continue (CI).

Il y a un secret concernant les tests fonctionnels. L’écriture de tests fonctionnels pour diagnostiquer les problèmes est une stratégie clé pour minimiser le temps de résolution des bugs. Toutefois, cette méthode de diagnostic ne fonctionne pas si l’écriture du test fonctionnel s’avère difficile. La solution ? Simplifiez les choses dès la première étape.

Créez une suite de fonctions utilitaires et d’assertions simples. Le but est de rendre les actions, telles que les boucles de relance et le fait d’importer des suites de données, TRÈS faciles à réaliser. Ces fonctions utilitaires devraient figurer parmi les premiers outils créés lors de l’écriture initiale du logiciel pour servir de support aux nouveaux tests fonctionnels par la suite.

Un exemple : comment nous avons fait cela chez Mailgun

L’API /messages principale chez Mailgun est gérée par un service que nous appelons influx. Ce service ingère à la fois les messages HTTP et SMTP dans notre système et communique avec presque tous les autres services de notre suite de services d’envoi de messages.

En conséquence, nous avons lourdement investi dans notre suite de tests fonctionnels et avons modélisé notre environnement de production dans le code pour cette partie très publique de notre catalogue de services. Nous avons créé plus de 700 tests, dont la plupart sont fonctionnels et s’exécutent en six minutes environ sur une machine locale.

Bon nombre de ces tests ont été ajoutés au cours de sessions de débogage ou de diagnostic, puis ont été intégrés à notre suite de tests. Si un nouveau scénario s’avère nécessaire, il est ajouté à la suite fonctionnelle et constitue une protection contre les futures régressions.

Dernières remarques

Le véritable coût du développement logiciel réside dans la maintenance et le diagnostic du code. C’est pourquoi se donner les moyens de réussir, en créant une suite d’outils utilisable pour réduire le temps de résolution, justifie cet investissement supplémentaire en temps et en argent.

Cet investissement initial continuera de porter ses fruits tout au long de la durée du projet.

Avez-vous apprécié notre point de vue sur la recherche et le diagnostic des bugs informatiques ? Si vous souhaitez davantage de contenus de ce type, n’oubliez pas de vous inscrire à notre newsletter.

Tenez-moi au courant ! Recevez d’excellentes ressources dans votre boîte de réception chaque semaine.
Envoyez-moi la newsletter Mailjet. J’accepte expressément de recevoir la newsletter et je sais que je peux facilement me désabonner à tout moment.

Découvrez la newsletter de Mailjet chaque mois dans votre boîte de réception.