mardi 1 juin 2010

La nuit, il faut dormir.

Soirée « correction de bugs ». Nous sommes quatre développeurs à rester ce soir. Le chargé de projet technique[1] est là lui aussi, ainsi que le chef de projet. Notre problème, c’est que divers comportements de l’application ne sont pas corrects : toutes les actions de l’utilisateur ne semblent pas prises en compte, ou pas correctement, si bien que l’état de l’application est incohérent[2]. Il arrive également que l’application plante : une fenêtre d’erreur Windows indique un problème de gestion mémoire[3], puis une fois sur deux, le PC reboote[4] de lui même.

D’après le rapport de l’équipe de test, le problème de comportement est reproductible sur tous les postes utilisés. Le problème de gestion mémoire, par contre, n’en concerne que deux sur les sept que cette équipe possède.

20h. Après un petit break d’une heure avec la journée « régulière », nous faisons le point. Le problème de gestion mémoire semble lié au matériel. Le problème de comportement, lui, semble plutôt résulter d’une erreur de conception ou de programmation : l’application ne plante pas, c’est sa logique de comportement qui est en cause. A moins que les deux choses soient liées, mais si on ne peut pas l’exclure, on ne peut pas non plus partir de ce postulat pour le moment.

Le chargé de projet technique nous distribue donc les tâches comme suit :
  • Un développeur, que l’on appellera D1 par la suite, se chargera de vérifier les entrées utilisateurs, c'est-à-dire la bonne prise en compte de ses interactions avec l’application.
  • Un développeur, D2, se chargera de vérifier les sorties, c'est-à-dire la conformité du comportement de l’application avec la réponse aux entrées générée par cette dernière.
  • D3 se chargera de vérifier ce qu’il y a entre les deux, c’est à dire la génération de la réponse applicative aux entrées utilisateurs et qui servira de base aux sorties.
  • D4, enfin, tentera de reproduire le problème de gestion mémoire remonté par les testeurs, en utilisant l’une des deux machines concernées. Il installera préalablement son environnement de développement sur cette machine. De cette façon, une fois l’anomalie reproduite, il pourra exécuter ligne à ligne le code source[5] concerné et pourra analyser le contenu de la mémoire au moment du problème.
  • Le chargé technique (CT), enfin, coordonnera les opérations et prêtera assistance aux quatre développeurs, sur demande.

Et moi, je suis D3.

21h. Je suis en train de rédiger différents scénarios de test permettant de valider ou d’invalider le comportement du générateur de réponses applicatives. D1 et D2 sont peu ou prou occupés à faire de même. D4, aidé de CT, sont en train de terminer l’installation de l’environnement de développement sur la machine des testeurs. Le chef de projet, quant à lui, est occupé dans son bureau. Nous savons qu’il aurait pu ne pas rester ce soir, mais qu’il le fait par soutien à son équipe. Nous l’en remercions intérieurement.

22h. D1 a repéré un problème mineur, mais qui ne peut expliquer le problème remonté. Il le corrige. D2 cherche toujours, en reproduisant ses scénarios de test. J’ai un doute, pour ma part : un comportement non spécifié, mais non proscrit par la spécification. Un « vide fonctionnel », en quelque sorte… J’en parle à CT qui se trouve aussi perplexe que moi. Nous tâchons de déterminer les conséquences de ce comportement, afin d’en estimer la capacité de nuisance mais aussi afin de s’assurer que le comportement fautif remonté par les testeurs ne soit pas parmi ces conséquences. D4 tente, en vain, de reproduire le problème de gestion mémoire.

23h. D1 a « refactoré »[6], une partie du module de gestion des entrées. Cela signifie qu’il a réécrit une partie de ce code afin de le simplifier sans que cela ne modifie son comportement. On considère en effet qu’à comportement égal, un code complexe tend à provoquer plus d’anomalie qu’un code simple car il est sujet à plus de cas particuliers, et plus difficiles à déceler. D2 a également trouvé quelque chose dans les sorties. Rien qui puisse tout expliquer, mais une anomalie se produisant dans certains cas particuliers et qu’il commence à corriger. CT et moi travaillons toujours à déterminer les implications du comportement non spécifié observé une heure plus tôt. D4 n’a toujours pas reproduit le problème et commence à désepérer.

23h30, petite pause décidée à l’unanimité. Nous allons fumer une deux cigarettes, baillons copieusement, puis faisons un point :
  • D1 nous explique son refactoring des entrées. Pas de commentaires, mais une approbation générale. Il en a encore pour une bonne heure, d’autres points mineurs ayant été identifiés.
  • D2 nous explique plus en détails l’anomalie trouvée dans les sorties. Celle-ci ne se produit que lorsque la réponse applicative servant de base aux sorties est dans un certain état.
    • J’objecte : la réponse applicative ne peut pas contenir les valeurs provoquant cet état. Si le cas se produit, c’est la réponse applicative elle-même qui est fautive.
    • D2 : « Ah ok, je ne pensais pas. Mais à supposer qu’elle le soit, il vaut mieux que la sortie ne plante pas ! »
    • CT : « Oui, c’est clair. Autant blinder[7], d’autant que t’as déjà commencé le refactoring il me semble. »
    • D2 : « Oui, j’ai presque fini en fait. »
    • D3 (moi) : « Tu pourras me donner des infos pour reproduire le cas que tu as observé ? Il faut que je corrige le générateur ».
    • D2 : « Ok, je te montre ça tout à l’heure. »
  • CT et moi décidons de réécrire la partie de code engendrant le comportement non spécifié décelé une heure et demi plus tôt afin d’éviter qu’il se produise. Nous soupçonnons ce comportement ambigu de générer des incohérences qui pourraient expliquer le problème de gestion mémoire sur lequel travaille D4, voire celui que rencontre D2.
  • D4 est fatigué de tester, tester, et tester encore sans parvenir à reproduire le problème mémoire remonté par les testeurs. Nous nous demandons si l’installation de l’environnement de développement sur la machine des testeurs ne prévient pas la reproduction de l’anomalie du fait de la modification de la représentation mémoire qu’elle engendre.

Minuit. C’est déjà demain. Nous nous y remettons.

1h30. D1 a terminé sa partie. Ses tests passent, tout va bien. La source des problèmes remontés n’était donc pas dans les entrées, tandis que des problèmes potentiels ont été fixés. Il rentre chez lui. Nous l’envions un peu, sauf D2 qui ne semble même pas s’être aperçu de son départ.

2h. D2 est en train de finaliser son refactoring. Il devrait en avoir terminé dans une demi-heure, ce après quoi il pourra me montrer le cas particulier dont on a parlé lors du point de 23h30. De mon côté, je viens de terminer réécriture de la partie du générateur qui provoque le comportement non spécifié : il ne se produit plus. Mais je n’ai toujours pas trouvé l’origine du comportement fautif remonté par les testeurs. D4 continue de tester en vain, mais nous annonce qu’il va bientôt abandonner.

2h15. Cri de joie de D4 : « Je l’ai ! La mémoire ne peut être ‘written’ ! ».

2h15’30’’. Cri de colère de D4. Son PC vient de rebooter. Le problème est reproduit, mais hélas dans sa version la plus radicale. « Et m****, c’est quoi ce ****** ? J’ai rien fait de spécial, j’ai rien eu le temps de noter, comment je vais le reproduire maintenant ? Je vais pas y repasser 3 heures ». En fait, non, cela en a plutôt pris six.

2h45. D2 m’explique rapidement le cas particulier évoqué plus tôt et qui fait planter la sortie. Il m’indique les valeurs d’entrée qu’il utilise pour le générer. Je les note pour tout à l’heure et lui demande s’il se prépare à rentrer. « Non, j’ai découvert un souci sur mon refactoring de tout à l’heure, il faut que je le corrige d’abord ». CT va lui prêter main forte.

3h. Je parviens à reproduire une génération de réponse applicative qui correspond au cas erroné remonté par les testeurs. Et ce malgré mon refactoring, donc. Les valeurs d’entrées ne correspondent pas à celles remontées par D2. Par contre, que se passe-t-il si j’essaie le cas de D2 ? Ai-je le même comportement ?

3h30. « P*****, je l’ai ! ». C’est D4 qui est parvenu à reproduire le problème, mais sans rebooter cette fois. Il note scrupuleusement l’état de sa mémoire à ce moment précis, ainsi que les paramètres utilisés.

4h. Le cas que m’a remonté D2 est différent de celui des testeurs. J’ai rapidement patché celui des testeurs par mise en place de blindages divers, c’était le plus simple à moins de tout refaire. En termes de programmation, ce n’est pas très beau, mais ça résous le problème. Par contre, le cas de D2 est plus complexe. J’ai de la ré-écriture à faire.

4h05. CT nous demande de terminer ce que l’on fait et de ne rien commencer d’autre. Il est temps d’aller se coucher. « Ok. J’ai presque fini ». Je ne sais plus trop, c’est peut-être moi qui ai dit ça.

4h30. Je m’emmêle les pinceaux dans mon code, et m’y reprends à plusieurs fois pour faire des choses pourtant simple. Je râle dans mon coin. D2 trace, trace et re-trace[8] son code, aidé par CT, son problème de refactoring n’étant pas réglé. D4, quant à lui, « blinde », à défaut de pouvoir régler la cause du problème. « J’y comprends rien, là. Ce cas là ne peut pas se produire, en théorie. Ecoute, je blinde en attendant de voir ça à tête reposée », explique-t-il à CT.

5h. CT : « Vous y êtes ?
- Oui oui, presque, deux secondes. », lui répond D4.

5h15. J’ai terminé. Fixer le cas soulevé par D2 a été difficile car impossible à reprendre sans modifier la conception, mais ça va, ce n’est pas trop mal fait au final. Je dis au revoir aux autres et je rentre.

Le lendemain, je me réveille vers 10h00. J’arrive au travail vers 11h00. CT est déjà là, ainsi que D1 et D4. J’ai un peu honte. « Vous êtes rentrés à quelle heure ?
- le temps de rentrer, vers 7h… On est parti vers 6h et demi, m’explique D4.

Plus tard, dans la journée, j’apprends que le cas soulevé par D2 et que j’ai fixé ne pouvait pas se produire : les entrées ne pouvaient fournir de telles valeurs. Comme D1 était déjà parti, nous ne nous en sommes pas aperçus et avons fait tout cela pour rien.

Le lendemain, nous apprenons par CT que le problème mémoire se produit toujours. D4 a bien fixé le problème, mais du fait de la fatigue, il n’a pas effectué de commit CVS[9], si bien que son correctif est resté sur son poste. Entre temps, les testeurs ont désinstallé l’environnement de dev de leur machine, si bien que tout son travail de cette nuit là a été perdu.

Quelques jours plus tard, les testeurs nous remontent de nouvelles anomalies. Plusieurs d’entre elles proviennent des refactorings effectués cette nuit là par D1, D2 et moi : des erreurs de programmation manifestement dues à l’inattention et la fatigue, mais aussi des erreurs de logique et d’appréciation de conséquences dues à la précipitation dans laquelle ces modifications ont été faites. Certaines corrections ont demandé plusieurs jours de travail. L’une d’entre elle a été corrigée par l’annulation du refactoring effectué et son remplacement par une version précédente contenue dans CVS.

Moralité : travailler « à l’arrache », de nuit, pour fixer des bugs ou terminer des travaux dans l’urgence, ça ne sert à rien d’autre que se donner bonne conscience. C’est même contre-productif, le nombre de problème générés pouvant dépasser le nombre de problèmes réglés. En outre, cela déstabilise l’équipe de développement, du fait de la fatigue et du décalage horaire engendrés.

Il s’agit donc d’une pratique à bannir.

Note : Cette histoire est basée sur des faits tout à fait réels, et n’a vraiment rien d’extraordinaire. J'ai du la vivre au moins dix ou quinze fois. Nombre de développeurs de votre entourage peuvent certainement vous raconter des histoires très similaires.


Notes
[1]Cette structure de projet comprenait une branche développement, dite « technique », et une branche IHM, composée de designers et de graphistes.
[2]Ce que l’on appelle ici « l’état de l’application », c’est la représentation mémoire que celle-ci à de l’utilisateur, de ses actions, et des réponses qu’elle génère en réaction à ces dernières.
[3]En presque-français dans le texte, la boîte indique « AppName.exe - Erreur d'application. L'instruction à "0x77F738a9" emploie l'adresse mémoire "0X0259200a". La mémoire ne peut pas être 'written' ». On aurait bien aimé qu’elle le puisse, pourtant…
[4]Nous sommes sous Windows 98 au moment où ceci se déroule. Aujourd’hui nous aurions un simple message d’erreur. Au pire un écran bleu, mais il faudrait se battre pour arriver à l’obtenir.
[5]Ce que l’on appelle code source, c’est l’ensemble des instructions qui constituent le programme, en langage compréhensible par le programmeur (ici, ce langage se nomme « C++ »). Le code exécutable, quant à lui, est une traduction de ce code source en langage compréhensible par la machine (et que l’on appelle parfois « langage machine »). L’application telle qu’installée et distribuée aux utilisateurs ne contient que le code exécutable. Faire le lien entre ce code et le code source nécessite une autre application, que l’on appelle ici « environnement de développement ».
[6]Et pardon pour ce barbarisme.
[7]« Blinder », en jargon, signifie prévoir que des cas aberrants puissent se produire pour les gérer de façon cohérente. Par exemple, si une probabilité se trouve à 1.5 tandis qu’elle ne peut normalement qu’être comprise entre 0 et 1, l’application la ramène à 1 avant de l’introduire dans ses calculs.
[8]« Tracer », c’est exécuter son programme non pas en le lançant normalement, mais manuellement, instruction par instruction. Ceci permet d’observer l’action exacte de chaque instruction, afin de solutionner un problème.
[9]CVS est un outil de gestion du code source. Le « commit » consiste à intégrer les modifications effectuées sur un poste de travail à la base de code source commune à tous les développeurs. C’est cette dernière qui est utilisée pour construire et livrer les versions de l’application. En l’absence de commit, la modification n’est visible que sur le poste de travail sur laquelle elle a été faite.

samedi 8 mai 2010

A quoi répondent les méthodes agiles ?

« RAD », puis « eXtreme programming », puis « Scrum »… On parle beaucoup des méthodes de développement logiciel dites « agiles » depuis les années 1995/2000. Ces méthodes se posent en réponse aux problèmes de rigidité rencontrés avec les méthodes traditionnelles de type « analyse-spécification-conception-programmation-tests-recette » - le fameux « Cycle en V ».

La méthode traditionnelle, c’est en quelque sorte le modèle de développement de l’Airbus A-400-M, où la programmation est remplacée par la construction et l’assemblage. L’actualité qui a entouré le développement de cet appareil eut été tout à fait applicable à un projet informatique : on présuppose que tous les tenants et aboutissants du projet sont maîtrisés dès son début, que ceux-ci sont invariants, et que les oublis, incohérences, et mauvaises surprises n’existent pas. Pourtant ils existent, et ils posent de nombreux problèmes techniques, économiques, mais aussi humains.

Ainsi - et ce n’est qu’un exemple - découvrir une ambigüité de spécification ou de conception alors que le projet entre en phase de programmation implique de « revenir à la planche à dessin ». Ceci est lourd de conséquences : le coût final et le délai de livraison s’en trouvent modifiés, et la responsabilité des intervenants est engagée[1]. S’il s’agit d’un projet spécifié par un client pour être réalisé par son prestataire, cela est d’autant plus impliquant : les pénalités, les délais de paiement ou les coûts engendrés peuvent compromettre le projet, mais aussi la survie du client ou du prestataire informatique. Ce risque pousse chaque acteur à « blinder » sa partie de façon à pouvoir exiger de l’autre le paiement des surcoûts en cas de mauvaise surprise : « C’est une incohérence de notre spécification, dites-vous ? Mais enfin, VOUS l’avez validée en tant que sachant. ». Ceci à un coût, prend du temps, est générateur de conflits et de blocages, alors que l’issue des conflits est souvent incertaine.

Les méthodes agiles ont donc intégré la notion de changement comme réponse à ces problèmes. Elles définissent des méthodes permettant de modifier la définition du produit en cours de développement sans pour autant revenir à la case départ. Pour ce faire, elles font intervenir le client à tous les stades du développement et non plus aux seules phases de spécification fonctionnelles et de recette et acceptation du produit fini (il existe dans le cycle en V un comité de pilotage régulier et qui implique le client, mais s’il a la possibilité de mesurer les écarts entre l’avancement réel et l’avancement prévu, il n’a pas réellement les moyens d’y remédier). Ceci est rendu possible par le raccourcissement du cycle de développement : schématiquement, un même produit, s’il est développé en un unique cycle en V d’une année, sera développé en 10 à 30 cycles Scrum ou eXtreme Programming. Ceci donne autant d’occasions aux équipes techniques et fonctionnelles de se réunir autour du produit en construction et de détecter une incohérence de spécification, un oubli, une infaisabilité technique, ou simplement de constater une évolution du besoin client, pour faire évoluer la définition du produit en conséquence.

Outre cela, certaines méthodes agiles telles eXtreme Programming définissent des pratiques techniques destinées à améliorer la maintenabilité du produit (qu’il s’agisse de maintenance corrective ou évolutive) ainsi que sa robustesse. D’autres, au contraire, laissent les pratiques techniques au libre choix de l’équipe de développement, pour ne s’intéresser qu’au suivi opérationnel du projet - c’est le cas de Scrum.

Attention cependant, car si la théorie de ces méthodes agiles est attrayante, leur pratique révèle quelques défauts et difficultés de mise en place :
- le client peut se montrer résistant au changement de mode de fonctionnement impliqué par la méthode (sans aucun jugement sur la raison de cette résistance)
- la spécification fonctionnelle telle que définie par le Cycle en V peut avoir valeur contractuelle, et donc demeurer nécessaire
- certains éléments des méthodes agiles peuvent s’avérer difficile à mettre en place car coûteux (programmation en binôme définie par eXtreme Programming), trop idéalistes (estimation des tâches Scrum faite par toute l’équipe et non par son responsable, ce qui suppose que chacun sait estimer la durée d’une tâche), ou simplement agaçants (« planning poker » : le client n’est pas là pour jouer et préférerait parfois entendre « réunion de définition de cycle de développement » - c’est que ça compte la communication !)

Appliquer une méthode « telle qu’écrite dans le livre » n’est donc pas forcément la meilleure solution[2]. Par contre, se l’approprier et l’adapter à ses besoins et contraintes peut donner d’excellents résultats : meilleure adaptation du produit aux besoins du client, rapport client/fournisseur facilité par l’acceptation du changement[1], et dans les meilleurs cas, réduction des coûts et délais. C’est précisément l’esprit de ces méthodes : rester proche des réalités et de la pratique, plutôt que de s’enfermer dans un raisonnement rigide car uniquement théorique.


Notes
[1]Ce précédent billet sur la gestion des changements d'avis du client en parle plus largement.
[2]D'où ce précédent billet sur l'application des méthodes agiles.

dimanche 25 avril 2010

Symantec AntiVirus Corporate Edition v7.5: régime pour un dévoreur d'espace disque.

Enfin ! Mon problème de disque dur est réglé.

Depuis quelques temps, Windows XP se plaignait régulièrement du manque d'espace sur mon disque C. Désinstallation des logiciels et composants inutiles[1], nettoyages à grand renfort de CCleaner [2] m'avaient alors permis de récupérer un bon Go. « Ça devrait aller », m'étais-je dit.

Et pourtant, non, ça n'allait toujours pas. J'ai injustement accusé divers composants, dont le système lui même, j'ai compressé certains répertoires, mais tous les deux ou trois jours, l'espace que je grapillais se trouvait immanquablement dévoré. Il y avait quelque chose d'autre.

L'idée de simplement remplacer mon disque dur par un modèle plus grand ne me satisfaisait pas. En toute logique, cela ne devrait faire que repousser le problème, simple question de temps. J'ai donc creusé, en inspectant la taille des différents répertoires de C, pour m'apercevoir que celui-ci, sous-répertoires compris, occupait 50% de l'espace du disque :

C:\WINDOWS\Profiles\All Users\Application Data\Symantec\Symantec AntiVirus Corporate Edition\7.5

Il s'agit du répertoire de travail du client Symantec AntiVirus Corporate Edition v7.5 installé sur ma machine[3]. A la racine de cette arborescence, des fichiers (.wdb, .iad, .iex, et .vdb) qui semblent être des mises à jour de définitions de virus. Deux sous répertoires suspect également : I2_LDVP.VDB\ et I2_LDVP.TMP\

A partir de là, c'est allé très vite : une recherche sur Google m'a mené à cette page du forum support de Symantec, sur laquelle un Trusted Advisor[4] explique que les fichiers peuvent être effacés sans risque, et que le sous-répertoire I2_LDVP.VDB\ peut être vidé sans danger.

Quelques hésitation tout de même avant de procéder. Mais au pire, j'étais bon pour une réinstallation du client de mon poste, le risque pouvait donc être pris.

Une fois l'opération effectuée, il y a une petite heure, mon disque C n'était plus occupé qu'à 60%. J'ai alors pu observer que le client Symantec récupère par téléchargement les fichiers de travail dont il a besoin. Ceci représente un volume modeste, et explique que l'effacement effectué ne l'empêche pas de travailler et ne génère pas de risque. Par contre, les définitions obsolètes, au lieu d'être effacées, sont laissées à l'abandon dans le répertoire de travail. Au fil des mois, cela représente un volume conséquent, et c'est pourquoi le support de Symantec conseille de les supprimer.

On peut juste regretter que ce ne soit pas automatique, ou mieux documenté !


Notes
[1]Dont les anciennes versions du runtime Java 5 et 6. Chaque mise à jour, même mineure, consiste en l'installation d'un runtime complet, soit une centaine de Mo, les versions antérieures devant être désinstallées manuellement. J'avais ainsi quatre ou cinq versions de Java 6.
[2]Je recommande vivement cet outil. Nettoyage du cache d'Internet Explorer, de Mozilla, de Chrome, nettoyage des programmes de désinstallations des patchs Windows (encombrants et pas destinés à servir), tout y passe...
[3]Cet anti-virus pour entrepris se déploie sur tous les postes de son réseau, mais s'administre de façon centrale depuis le poste de son administrateur.
[4]Il s'agit d'un membre du forum que Symantec reconnait capable de renseigner les utilisateurs. C'est donc une source fiable d'après l'éditeur du produit.