Les bogues ne sont pas une fatalité: ce sont des défauts dus à des erreurs d'êtres humains:

  1. Trop grande complexité
  2. Méconnaissance des API ou du langage
  3. Étourderies
  4. Laxisme !

Comme vous êtes comme moi et détestez passer vos après-midi sur un débogueur, voici quelques stratégies éprouvées pour les réduire.

10. Documentez

Je vais ici seulement insister sur les aspects de la doc qui permettent de limiter les bogues :

  • Rédigez une partie de la description d'une méthode avant de la coder
    Vous devez réfléchir aux cas particuliers du déroulement, aux valeurs possibles des arguments d'entrée (notamment comment gérer les gammes de valeurs inattendues), et quelles erreurs peuvent subvenir pendant l'exécution. Inspirez-vous de la programmation par contrat.

  • Faites des commentaires utiles
    Ils ne doivent pas paraphraser le code, ni expliquer chaque appel de méthode. Expliquez le cheminement du code, et soulignez ce qui est le moins évident. C'est très utile au débogage, quand on revient au code quelques semaines plus tard.

  • Nommez vos variables avec grand soin
    Je suis toujours étonné qu'à notre époque, les gens nomment encore leurs variables i,j, tab[] ou ptr, dans la plus pure tradition du Kernighan & Ritchie. Il m'est arrivé de rendre des bogues évidents rien qu'en renommant.

9. Relisez-vous

D'expérience, la moitié des bogues pourraient être évités lors de l'écriture du code. Je ne parle pas des erreurs de syntaxes (signalées par le compilateur), mais utiliser une variable à la place d'une autre, effectuer les opérations dans le mauvais ordre, etc.

Si vous programmez seul, pensez à relire — avec un œil critique s'entend — votre nouveau code systématiquement avant de compiler.

8. Corrigez les bogues dès qu'ils apparaissent

Il arrive fréquemment de découvrir un bogue, et de le laisser à plus tard. Comme vous êtes un programmeur consciencieux, vous y reviendrez. Mon expérience dicte de corriger le bogue immédiatement, parce qu'il arrive souvent de commettre encore et encore la même erreur. Mieux vaut donc la corriger au plus tôt, pour ne faire l'erreur qu'une fois.

7. Testez les codes d'erreur et gérez les exceptions

À moins que cela n'alourdisse exagérément le code, il est sage de vérifier les résultats des fonctions. Gérez aussi les exceptions. Ces deux tâches triviales ne doivent pas être laissées à plus tard — c'est à dire jamais.

6. Créez des projets d'essai

Bien que je travaille sur un gros projet, je crées de nombreux petits projets annexes. Le dernier en date était destiné à vérifier sur mon site web si une nouvelle version de l'appli est disponible. Je n'avais aucune expérience du téléchargement d'un fichier texte. Sur ce petit programme de vingt lignes, j'avais tout de même commis deux bogues. Avoir un programme léger permet de les cerner rapidement.

Si j'avais intégré la fonction directement dans mon appli, je me serais demandé si les problèmes venaient du nouveau code ou de l'ancien. J'aurais peut être modifié du code valide. Enfin, maîtriser une technologie permet de savoir comment l'intégrer au mieux au reste de l'application.

5. Ayez des scénarios de test sous la main

D'expérience, il arrive que les bogues apparaissent dans des circonstances déjà rencontrées. Lorsque vous avez trouvé une procédure pour reproduire un bogue à coup sûr, notez-là et déroulez cette procédure à chaque version. Si possible, automatisez les tests. Par exemple, les tests de l'interface utilisateur peuvent être automatisés avec Instruments.

4. Écrivez des tests unitaires

Les tests unitaires s'appliquent essentiellement aux classes de la couche modèle. Ma première expérience fut une classe que je n'arrivais pas à mettre au point pour un programme de musique, qui devait renvoyer les notes faisant partie d'une gamme. J'étais capable de trouver ces notes sur le papier, mais je m'y suis repris à trois fois avant d'avoir un algorithme… qui ne marchait pas dans certains cas particuliers.

Après avoir écrit les tests unitaires, les erreurs apparaissaient de façon évidente dans la couche modèle, plutôt que de manière indirecte dans l'interface utilisateur. J'ai gagné du temps, et j'étais enfin sûr de mon code.
Nous reviendrons sur les tests unitaires, à la mode, et ceci pour d'autres très bonnes raisons.

3. Réduisez les contraintes

Comme dit plus haut, la cause principale des bugs est la complexité. En effet, des scientifiques ont mesuré que les individus les plus doués étaient capables de prendre en compte simultanément sept contraintes. Pour la plupart des gens, c'est plutôt quatre (<- c'est là que je me situe).

L'idée est donc de limiter la complexité du logiciel:

  • en ajoutant de l'abstraction
    pour pouvoir se concentrer sur la tâche présente au lieu des détails d'implémentation.

  • en limitant le couplage
    c'est à dire les interdépendances entre objets.

Et cela est difficile. Très. Beaucoup de gens se disent programmeur ("oui, j'ai étudié un peu le langage C pendant mes études de biologie"), mais peu ont déjà travaillé sur un projet conséquent, un où les bogues deviennent difficiles à résoudre et semblent réapparaître sans cesse.

Je n'ai pas de secret, il faut se forger une expérience: étudier les patrons de conception, savoir comment fonctionne la machine et le système d'exploitation, étudier d'autres langages de programmation ou d'autres bibliothèques…

2. Ne construisez que les infrastructures nécessaires maintenant

Ce point est un peu en contradiction avec le précédent. En effet, quand on commence à ajouter de l'abstraction, on a ensuite tendance à vouloir rendre tout réutilisable et générique. Imaginons que vous vouliez écrire un texte en rouge. Il est tentant d'écrire une méthode générique qui prend la couleur en paramètre, pour le jour où vous voudriez écrire en vert.

Pourtant, la méthode générique sera forcément plus difficile à écrire et à tester. Pourquoi passer du temps sur quelque chose d'inutile aujourd'hui ? Attendez un jour prochain que le besoin soit réel. De plus, une méthode simple sera une bonne base de départ pour écrire une méthode plus complexe.

1. Codez moins

Aucun code n'est plus flexible que pas de code.

Il existe souvent une méthode qui rend 80% du service avec seulement 20% du travail que demanderait une méthode parfaite. Ne visez pas la perfection. La vie est trop courte ! Demandez-vous si vos utilisateurs préfèrent une fonction parfaite dans un an ou une fonction correcte aujourd'hui.

Moins de code = moins de bogues
Travaillez le cœur de métier de votre logiciel. Les détails seront réglés plus tard (voire jamais).

Pour finir

Voilà les méthodes que j'utilise — avec plus ou moins de rigueur — pour conserver mes applis stables. Auriez-vous d'autres techniques à proposer ?