samedi 28 novembre 2009

Des manchots aux pommes

Jusqu'à présent, j'étais plutôt adepte des PCs sous Linux. Depuis peu, cela est en train de changer grâce à l'acquisition d'un MacBook.

J'ai toujours considéré les Mac comme des machines de qualité, mais trop chères pour ce qu'elles étaient, malgré tout. La dernière gamme de MacBook est venue à bout de cette idée : ce sont d'excellentes machines pour un excellent rapport qualité/prix. Et qui fonctionne sous Unix. N'oublions pas que le cœur de Mac OS est un Free BSD, remanié par Apple, mais la base est là. Vous avez toujours accès au terminal, avec l'ensemble des commandes Unix. X11 est là, lui aussi. Il y a quelques années de ça, un ami avait acheté l'iMac Tournesol. Un des arguments qu'il avait donné pour expliquer son choix était, entre autres : "je peux lancer un terminal et utiliser vi". Je le comprends très bien aujourd'hui et le rejoins complètement. C'est un peu le meilleur des deux mondes.

Et les logiciels libres dans tout ça ? Le projet Macports mets plus de 6300 paquets logiciels à disposition, façon apt sous Debian ou Ubuntu ou plutôt à la manière des ports sous BSD.

Voici donc le début de mes aventures sur les ordinateurs à la pomme. Je ne renie pas pour autant les manchots et, d'ailleurs, si je ne suis pas totalement satisfait, je reviendrai vers Linux qui reste, pour moi, une valeur sûre.

mercredi 18 novembre 2009

Cachez cette erreur que je ne saurais voir !

Qu'y a-t-il de plus agaçant qu'une application qui plante lamentablement sans le moindre message ? Qu'y a-t-il de plus compliqué pour le développeur à déboguer que ce type d'application ?

Ça fait assez longtemps que j'observe cette tendance qu'il y a chez de nombreux développeurs (y compris moi-même à une certaine époque) à cacher les erreurs, les exceptions, à tout faire pour que l'application ne quitte pas inopinément, au risque de mettre en péril la fiabilité des résultats obtenus. On essaie de faire croire à l'utilisateur/client qu'il n'y a jamais de problème. Mais tout développeur sait que cela est illusoire. Une sorte Graal qu'il est impossible d'atteindre. On a beau utiliser des tests unitaires, essayer de prévoir tous les cas d'erreurs, il y a toujours une situation qu'on n'a pas anticipée et qui plante notre application qu'on croyait pourtant si robuste.

J'ai pu également observer qu'on a l'air beaucoup plus crédible et professionnel lorsqu'on a une trace ou un code d'erreur lorsqu'il y a plantage. L'idéal est de pouvoir produire une image de la pile dans un fichier au moment de l'erreur, et de la demander au client : vous gagnez en crédibilité car vous avez envisagé l'improbable. De plus, vous récupérez des informations précieuses qui vous permettent de comprendre et de corriger rapidement le bug. Un code d'erreur est souvent suffisant.

Les assertions (assert), par exemple, peuvent être un outil précieux. Lorsque je débutais, on m'a appris, comme une sorte de dogme, que les assertions ne devaient être utilisées que dans du code compilé en version debug, mais ne devaient pas apparaître dans la version compilée en release. Je me rends compte maintenant, pour avoir violé cette règle, combien cela est une erreur. Une assertion sur un pointeur NULL à un endroit où on ne s'y attend pas et qu'on ne sait pas rattraper, par exemple, évite une erreur anonyme. Un assert indique la ligne de code et la condition qui a provoqué l'arrêt. Une information des plus précieuse pour corriger rapidement une erreur grave.

À mon sens, le plus important est la maîtrise de l'erreur. Avoir le contrôle et le montrer, pour gagner la confiance du client ou de l'utilisateur. Bien entendu, aucune erreur, c'est mieux !

mardi 17 novembre 2009

Le code jetable

On a tous eu un jour à écrire un bout de programme pour tester une idée, une librairie, un concept ou pour faire une "maquette". En général, on se dit que, de toutes manières, on ne gardera pas ce qu'on est en train d'écrire. Alors on met de côté la qualité.

Avez-vous remarqué que ce code destiné à être oublié finit très souvent en production ? La maquette évolue pour devenir le produit final. La partie utile du petit programme de test est intégrée telle quelle dans une application. Avec un impact sensible sur la qualité de l'ensemble.

Et lorsque ce code n'est pas directement utilisé, il sert souvent d'exemple, voire de documentation pour les nouveaux arrivants ou les collègues. Et il est pénible de passer beaucoup plus de temps qu'il n'en faut sur un exemple parce qu'il est incompréhensible ou qu'il est impossible de le faire fonctionner.

Ce qui m'amène à la pensée du jour : lorsque vous écrivez un bout de programme, essayez d'imaginer ce qui se passera lorsque vous devrez le reprendre dans 1 mois, 3 mois, 6 mois, 5 ans.

lundi 16 novembre 2009

Un peu de Vim

Si comme moi vous développez en C++ sous Vim (le meilleur éditeur de la Terre, il faut bien le dire), et que vous avez la chance d'avoir un écran large avec une résolution suffisante pour ouvrir deux fichiers sources côte à côte, alors ces petits raccourcis vont vous plaire :


noremap :exe "vert sfind ".substitute(expand("%:t"),
expand("%:e")."$", "cpp", "")
noremap :exe "vert sfind ".substitute(expand("%:t"),
expand("%:e")."$", "h", "")


Le premier permet d'ouvrir le fichier cpp correspondant au fichier h que vous êtes en train d'éditer. Je vous laisse deviner ce que fait le second. La fenêtre sera coupée en deux verticalement, ce qui permet d'avoir les deux sous les yeux.

Combiné avec les excellents scripts cvim et taglist, le développement en C sous vim, c'est que du bonheur !

samedi 14 novembre 2009

Copier-coller, c'est mal !

On nous l'a souvent dit, la duplication de code est une mauvaise chose. Les raisons les plus souvent invoquées sont également les plus évidentes :
  • elle rend la maintenance et l'évolution du code beaucoup plus difficile,
  • elle alourdit et complexifie inutilement ce même code
Mais nombreux sont les développeurs qui ne prennent pas réellement ces défauts au sérieux, voire qui ne les comprennent pas. "Pourquoi se compliquer la vie ? Mon code fonctionne". Pire encore certains pensent qu'il est bon de dupliquer le code plutôt que de le factoriser dans des fonctions ou des méthodes pour des raisons de performances. Combien de fois avez-vous entendu à propos d'un bug enfin expliqué : "ça vient d'un copier-coller" ? Ou encore : "Oh ! Ça, ça devrait aller vite, c'est copier-coller de telle autre méthode" ?

J'ai été amené à intervenir sur de nombreux styles de code différents, dans pas mal de langages différents. J'ai vu du bon code, et du mauvais, aussi. Mais je crois que ce qui m'a posé le plus de problème, c'est bien le code dupliqué, le "copier-coller". J'ai eu de plus l'occasion d'observer ses méfaits sur des applications ayant plusieurs années de développement derrière elles.

J'aimerais essayer ici de partager cette expérience.

Il y a plusieurs niveaux de duplication :
  • au sein d'une fonction/procédure/méthode (appelons cela une fonction, pour faire simple),
  • dans un module/une classe,
  • dans un projet qui comprend plusieurs modules.
Au sein d'une méthode ou d'une classe, le code dupliqué est facile à voir. C'est également, à mon sens, son principal défaut : il transforme une fonction qui devrait, conceptuellement, faire une vingtaine de lignes en un monstre de parfois plusieurs centaines de lignes. Il noie l'information. Il est lors très difficile pour quelqu'un qui ne l'a pas écrit de se plonger dans ce type de source. Souvent, même son auteur a du mal à revenir dessus. Et si il y a une modification à apporter, même mineure, l'effort que cela demande est souvent disproportionné du fait qu'il faut la reporter autant de fois que le bloc de code à modifier a été dupliqué. J'ai vu des bugs qui résultaient clairement d'un oubli lors du report d'une modification sur des blocs dupliqués. Un des cas extrêmes que j'ai pu rencontrer est l'utilisation d'une dizaine de variables de type a_0_1, a_0_2, ... , a_5_1, a_5_2 à la place d'un simple tableau à deux dimensions. Je vous laisse imaginer l'allure des fonctions qui ont la charge de l'initialisation et de la mise à jour de ces variables.

J'ai encore du mal à comprendre ce qui peut pousser un développeur abuser du copier-coller dans une fonction.

Il semblerait que certains pensent que leur code sera plus efficace sans appel de fonction. Dans l'absolu, cela est probablement vrai. Et cela est une réelle contrainte dans certains systèmes embarqués dans lesquels la profondeur de la pile d'appels est limitée. Mais soyons raisonnables : les machines actuelles sont performantes. Nous ne sommes plus à l'ère des machines 8 bits pour lesquels il fallait grappiller des cycles de processeur partout où cela était possible. Les quelques nanosecondes supplémentaires que coûte un appel de fonction n'aura pas d'impact sur les performances de votre application. Et si vous developpez en C++, les méthodes inline sont là pour ça. L'optimisation des performances passera plus certainement par un travail sur les algorithmes utilisés.

Si c'est pour éviter de taper au clavier, alors je pense qu'il faut penser soit à changer d'éditeur de texte, soit à apprendre à taper (ce n'est pas si dur et il est facile de faire des progrès), soit à changer de métier. Mais j'ai du mal à croire que la flemme d'utiliser son clavier puisse réellement motiver l'abus de copie.

Bien pire que la duplication au sein d'une méthode ou même d'un fichier, il y a la copie de blocs dans divers fichiers sources, qui peuvent être répartis dans plusieurs modules (ou librairies, ou packages, ou ce que vous voudrez). C'est là que les choses se compliquent. On retrouve le problème de la répercussions des modifications sur un des blocs de code. Cela est d'autant plus vrai si on travaille en équipe et que la personne qui fait la modification n'est pas consciente de la duplication. On voit alors des bugs que l'on croyait corrigés resurgir là où on ne les attendait pas : la correction a bien été faite, mais pas partout. Une nouvelle correction est alors apportée, souvent différente de celle qui a été faite sur l'autre portion de code, celle qui a servi de modèle. Et on se retrouve avec des lignes de programme qui ont l'air bien différentes mais qui font, au final, la même chose. Et avec un programme dont les sources sont bien trop lourdes par rapport à ce qu'elles devraient être. La maintenance devient pénible et les évolutions trop difficiles. Alors on se dit qu'il faut tout refaire et on recommence un cycle.

Il est beaucoup plus simple de créer des librairies ou des classes qui contiennent ce code commun. Et ce n'est pas réellement beaucoup plus long. Dans la plupart des cas l'argument du temps n'en est pas un.

Mais bien au delà des conséquences sur le code, je pense que l'abus de copier-coller a également un impact sur le développeur lui-même, qui perd peu à peu l'habitude d'innover et de chercher des solutions. Combien de fois, face à un nouveau problème, le premier réflexe est de voir si quelqu'un d'autre n'a pas déjà écrit un programme pour le résoudre, et de le copier sans même réellement chercher à savoir si cette solution est la bonne dans ce contexte. Chercher à ne pas réinventer la roue en permanence est une bonne chose. Réutiliser ce que d'autres ont déjà fait n'est pas mauvais en soit, c'est le principe même des librairies. Mais tenter de développer en mettant bout à bout des portions de programmes piochées à droite et à gauche est une mauvaise idée. J'ai vu un stagiaire échouer en s'y prenant comme ça, par tâtonnement, sans vraiment comprendre ce qu'il faisait. Alors qu'il aurait certainement réussi en prenant le temps de chercher et de comprendre. Son responsable lui a fait confiance et ne s'en est pas aperçu à temps. Une fois le stage terminé, il s'est avéré que ce qu'il avait annoncé comme impossible était en fait faisable, et même plutôt simple.

Il y aurait encore beaucoup à écrire sur le sujet. Mais j'espère que la lecture de ce billet vous fera réfléchir avant de copier un bout de code pour le recoller quelque part.