Une remarque pertinente ?
Une critique impertinente ?
Un lynchage en règle ?
Une invitation sous les tropiques ?

Ecrivez-moi !

Conçu et enseigné tel qu'en lui même, avec pertes, fracas et humour de qualité supérieure            
 par Christophe Darmangeat dans le M2 PISE du Master MECI (Université Paris 7)            




Partie 8

La chasse aux bugs

Les bugs (en anglais : « sales petites bébêtes qui pourrissent notre belle vie de développeurs ») se répartissent en trois différentes espèces ; chacune ayant des mœurs différentes, les techniques de chasse, les appâts et les pièges devront être adaptées à chaque type de bestiole.

1. Les erreurs de syntaxe

Nous avons vu que l'éditeur de code de C# signalait en temps réel les erreurs de syntaxe (et, plus généralement, les erreurs de compilation). Qui plus est, il précise la nature de ces erreurs sous forme d'info-bulle. De ce côté, donc, pas de souci, voilà une affaire réglée : les bugs de syntaxe seront la première espèce à être repérée, puis exterminée par vos bons soins. Je ne m'appesantis donc pas sur ce point.

2. Les erreurs de logique

Là, c'est tout de suite une autre paire de manches. L'erreur de logique, animal sournois s'il en est, ne provoque pas forcément un plantage de l'application. Elle se contente souvent de générer un résultat aberrant, ou tout simplement faux, ce qui la rend d'autant plus difficile à débusquer.

Ainsi, si un tour de boucle de trop dans le balayage du tableau se traduira immédiatement par le plantage du programme, le tour de boucle qui manque dans le même balayage peut très bien fausser toute la suite sans qu'on se rende compte de rien. Et je ne parle pas de l'erreur de calcul de 4,23 % dans le surcoût de l'échelon inférieur de la prime d'huile de l'ouvrier qualifié à l'échelon 543 modifié 42-B. Celle-là, il faut drôlement avoir l'œil pour la repérer.

Pour traquer cette engeance, C# propose plusieurs armes redoutables, pour la plupart disponibles dans la barre d'outils Déboguer.

  • L'exécution pas à pas : pour commencer, la première question qu'il faut se poser (et qu'on oublie beaucoup trop souvent) : le programme passe-t-il bien sur les lignes de code que nous avons écrites, et le fait-il au bon moment ? Un bon moyen de le savoir est de demander une exécution pas à pas. L'exécution ira alors de ligne en ligne, en n'avançant que lorsque nous lui en donnerons l'ordre formel, via la touche F8. Cette technique permet de repérer tout de suite les procédures dans lesquelles on ne rentre pas, et les mauvais branchements en général.
  • Le point d'arrêt : si on soupçonne un souci dans la procédure tout au fond en bas à droite derrière l'église, il peut être fastidieux de se fader dix minutes de pas à pas avant de parvenir à l'endroit suspect. Pour cela, une technique simple : le point d'arrêt, judicieusement posé d'un clic de souris dans la marge, juste avant l'endroit où les ennuis arrivent. On lance une exécution normale, qui s'interrompt au point d'arrêt. Et à partir de là, on peut y aller au pas à pas.
  • Un déboguage consiste souvent à pouvoir suivre au fur et à mesure de l'exécution les valeurs successives prises par une variable rétive. A cette fin, l'outil le plus simple et le plus puissant proposé par C# est tout bonnement... la souris ! En effet, en exécution pas à pas (et souvent, en ayant placé judicieusement un point d'arrêt), il suffit d'amener la souris au-dessus d'une variable ou d'une expression pour qu'apparaisse, dans un petit cadre jaune, la valeur de cette variable ou de cette expression. Avec un peu d'habitude, c'est incroyable ce que ce petit truc peut rendre service :

3. Les erreurs d'exécution

Malgré tous nos efforts, et nos qualités de chasseurs, et même si notre code est optimisé aux petits oignons, il reste une espèce de bugs que nous ne serons peut-être pas parvenus à éradiquer : les erreurs d'exécution.

Imaginons par exemple que notre application permette d'aller chercher un fichier quelque part sur la machine, avec une boîte de dialogue du genre du très classique Fichier - Ouvrir. Même en admettant que notre code soit parfait, nickel-chrome, s'il prend l'idée à l'utilisateur d'aller chercher un fichier sur le CD-ROM alors que le lecteur est vide, ou de taper le nom d'un fichier qui n'existe pas, notre application se vautrera lamentablement en générant un message incompréhensible et très effrayant.

Or, il faut toujours garder en tête les principes fondamentaux du développeur :

Principe n°1 :
Si une action de l'utilisateur, quelle qu'elle soit, est susceptible de faire planter une application, la probabilité qu'un utilisateur lambda accomplisse cette action tend vers l'infini très très vite.
Principe n°2 :
Si le développeur est convaincu qu'aucune action de l'utilisateur n'est susceptible de faire planter son application, la probabilité qu'un utilisateur le détrompe tôt ou tard est proche de 100%.

D'où la nécessité absolue, lorsqu'on conçoit une application à vocation un peu sérieuse, d'utiliser l'arme ultime : le piège à erreur d'exécutions, c'est-à-dire le piège à exceptions.

Définition  :
Une exception est une erreur d'exécution.
C'est aussi une classe de C#, qui si une erreur d'exécution se produit, va générer un objet (dans son jargon, C# dit alors qu'une exception est levée).

L'idée de base, pour gérer ces erreurs, est d'intercepter l'objet Exception créé en cas d'erreur, d'éviter ainsi que cet objet interrompe l'exécution, et de dire à C# ce qu'il doit faire alors, au lieu de se planter. Cela s'effectue par l'écriture d'un bloc Try ... Catch.

Remarque  :
Tout cela rappelle très furieusement ce qu'on a vu depuis longtemps à propos des conversions de types, et de la méthode TryParse que j'avais appelée « technique avec filet ».

La structure la plus simple du piège à erreur est la suivante :

try
   {
   instructions susceptibles de provoquer une erreur
   }

Cela consiste à dire à C# : « essaye d'exécuter (pour de faux) les instructions suivantes. Si tu constates qu'elles sont susceptibles de te faire planter, alors ne les exécute pas pour de vrai. Si tu vois que tout va bien, pas de problème, vas-y pour de bon. » Le truc, c'est qu'avec ça, en cas de souci, le programme se contente de ne rien faire. C'est beaucoup mieux que s'il se cassait la figure, mais cela peut être insuffisant.

Aussi ajoutera-t-on souvent un bloc d'instruction chargé de s'exécuter uniquement en cas d'exception, et qui dira à la machine ce qu'elle doit faire si une telle situation survient :

catch
   {
   instructions à exécuter en cas d'exception
   }

L'écriture précédente a encore un défaut : elle s'exécute quelle que soit la nature de l'erreur. Or, on ne souhaite peut-être pas faire réagir la machine de la même façon s'il s'agit d'un fichier non trouvé ou d'une division par zéro. Voilà pourquoi, dans le code qui précède, on aura souvent tendance à préciser un argument à Catch, pour dire à quelle exception spécifique le code est censé réagir. Ainsi, en cas de fichier non trouvé (exception d'input/output), écrira-t-on :

catch (System.IO.IOException e)

En cas de fonction appelée avec un argument invalide :

catch (System.ArgumentException e)

etc.

On peut écrire à la suite autant de blocs catch que l'on souhaite, pour chercher à « attraper » différents types d'erreurs. L'idée sera alors de commencer par écrire les catch correspondant aux types les plus précis, pour mettre à la fin les types les plus généraux (sinon, ceux-ci courtcircuiteraient ceux-là, j'espère que c'est clair).

Bon, avec ça, vos applications n'ont plus aucune excuse pour se planter sur une erreur, quelle qu'elle soit.

Et vous non plus.