Making Wrong Code Look Wrong : ce billet de mai dernier de l’excellent Joel Spolski porte sur les niveaux de lecture du code informatique, les normes de codages, la colocation, comment faire apparaître un bug à la lecture, sur la justification de la notation hongroise, et les dangers des exceptions.

Détaillons, traduisons, commentons :

Propreté

La propreté d’un environnement de travail dépend du but poursuivi et ne saute pas aux yeux d’un profane (Joel donne l’exemple d’une boulangerie). Seuls les gens expérimentés repèrent quels sont les objets/machines à surveiller/graisser, et ne font pas attention à la peinture défraîchie professionnellement sans intérêt.

Le débutant (ou le chef non issu du milieu) ne verra que l’extérieur : la peinture défraîchie (mais pas la machine bien graissée), le bureau mal rangé (mais sur le disque dur c’est au cordeau), le code qui ne respecte pas les normes de codage (indentation, nommage des variables, etc.)

Ce n’est qu’avec de l’habitude que le débutant repère les pièges qu’aucune norme n’évitera (Joel fournit quelques exemples vicieux en C). Le professionnel expérimenté sent les pièges invisibles au débutant.

Les erreurs qui sautent aux yeux

Le niveau encore supérieur est de construire le code pour que les erreurs sautent aux yeux. Suivent quelques exemples, notamment la proposition d’utiliser des préfixes comme dans usMaChaîne pour toute chaîne venue de l’extérieur non encore passée au filtre sécuritaire (contexte d’une appli web où chaque chaîne envoyée doit être considérée comme hostile et menant à une injection SQL, jusqu’à ce qu’elle soit déclarée innocente).
C’est une sorte de typage destiné au développeur.

Pour faire apparaître à l’œil le code incorrect, une autre technique est de s’arranger pour ne pas avoir besoin, dans un bout de code, d’aller voir ailleurs.

  • D’où la technique précédente qui évite de chercher le type jusque dans les déclarations.
    D’où l’intérêt de faire des fonctions courtes (un écran), et des embranchements de conditions et des boucles que l’on peut embrasser d’un regard.
  • D’où une déclaration des variables le plus près possible de l’endroit où elles sont utilisées (colocation).
    (Et pour ma part, je vois un autre intérêt : elles sont le plus locales possible, le moins sujettes à modification par effet de bord. Dans l’idéal, une variable utilisée à chaque pas d’une boucle est déclarée dans la boucle, réinitialisée ainsi à chaque itération. Hélas, l’ABAP, entre autres, ne le permet pas, et c’est une des raisons de ma répugnance à l’utiliser.)
  • J’ajouterai aussi que faire modifier des variables globales par des fonctions dont elles ne sont pas paramètres est une abomination : les paramètres doivent indiquer ce qui entre dans une routine, ce qui en sort, sans toucher au reste.
  • Un code doit faire ce qu’il semble vouloir faire : macros et surcharge des opérateurs sont à proscrire.

Notation hongroise

Joel revient à une méthode très souvent décriée : la notation hongroise.

À l’origine, son inventeur proposait d’inclure un préfixe dans le nom de la variable lié non à son type mais à son genre (kind), son utilité fonctionnelle :
usChaine indique une unsafe string (dans l’exemple du code web ci-dessus), et sfChaine sa version sécurisée ;
rw et col sont, dans le code d’Excel, respectivement des lignes (rows) et colonnes ;
xw, yw, xl et yl indiquent des abscisses et des ordonnées par rapport à la fenêtre (window) ou au cadre d’affichage(layout) ;
c indique un comptage et d une différence ;
bref toute technique où un mélange direct saute aux yeux, car toute conversion doit être explicite (sfChaine = SafeFromUnSafe (usChaine) ou xwTruc = xlTruc + xwLayout).

La notation hongroise a tant d’ennemis parce que les développeurs pour Windows, notamment, en ont abusé (suivant les premiers manuels), et le préfixe n’indique en général que le type technique (i pour int, dw pour double word...), information inutile car le compilateur sait déjà repérer les erreurs d’affectations les plus grossières.
(Du moins à deux réserves près :

  • Les avertissements du compilateur doivent être activés, ce qui paradoxalement n’est plus le cas dans les gros projets à l’historique chargé (Word par exemple, selon un commentateur de Slashdot ayant touché au code).
  • Les conversions implicites existent et pourrissent la vie de bien des programmeurs, en générant certains bugs vicieux.
    En PL/SQL, mes favoris reposent sur les conversion de date (TO_CHAR) sans préciser le format (il faut plutôt par exemple TO_CHAR(madate, ’DD-MON-YYYY’)), ou en ABAP les nombres stockés sous forme de chaînes de caractères (ce qui flanque facilement tout tri ou filtre en l’air). )

(Personnellement, je pense qu’une notation qui indique déjà le type technique est mieux que rien. J’aime savoir au premier coup d’œil si je manipule un tableau, une chaîne, un nombre. La notation hongroise utilisée à bon escient indique à quoi ils servent, en plus.)

(J’ajouterais aussi qu’il est possible dans certains langages de créer des types en fonction des besoins. L’exemple le plus primitif est mon cher ABAP, où par exemple il existe un type KUNNR pour les codes clients[1] (« élément de données » défini comme un CHAR de taille 10), ou MATNR pour les codes articles (CHAR de taille 18).
Dans le vieux R/3 on ne profite pas des vérifications possibles, et l’intérêt est limité. Le module CRM, en ABAP objet, est bien plus strict, et impossible de fournir à une fonction un type quand elle en attend un autre, même si en réalité le type sous-jacent est le même. La conséquence est une orgie de variables temporaires de transtypage, et beaucoup d’énervement pour le développeur, car ces incompatibilités de types ne se révèlent pas à la compilation, mais sous la forme de dumps du programme à l’exécution !!!!
Même sans cela, un typage trop fort imposé par le compilateur devient vite fastidieux.)

Exceptions

Joel termine par sa haine des exceptions (si courantes en Java[2]). Elles éliminent la colocation du code, qui n’est alors plus linéaire et facile à suivre. Comme un haïssable GOTO, il permet de court-circuiter toute la logique du programme.

(En PL/SQL, j’utilisais les exceptions de manière très locale pour tester le résultat d’un SELECT (test de NO_DATA_FOUND, TOO_MANY-ROWS...), ou dans les cascades de tests de validation, seul endroit aussi où un GOTO fin_test peut être employé légitimement pour des raisons de lisibilité).

Pour finir

Sa conclusion : The way to write really reliable code is to try to use simple tools that take into account typical human frailty, not complex tools with hidden side effects and leaky abstractions that assume an infallible programmer.

Et en tant qu’humain hautement faillible, je ne peux qu’approuver.

Notes

[1] KUNde NummeR, auf deutsch, natürlich.

[2] Apparemment jusqu’à l’absurde.