16 nov. 2006

Mesurer la lisibilité du code

Qu'est ce qui rend le code lisible ? La notion même de lisibilité du code est vague et confuse, à tel point que chaque personne en a sa propre définition. Quand on parle de lisibilité de code, on entre dans le domaine du subjectif. Il s'agit pour chacun de définir sa propre façon de comprendre le code, c'est à dire comment chacun construit sa propre abstraction.

Dans A Theory of Fun for Game Design[1], Koster analyse la manière dont notre cerveau, alimenté par notre quotidien, construit les abstractions. Il en déduit que notre cerveau est une redoutable machine à reconnaitre les motifs - si vous lui présentez le dessin ci-dessous, il reconnaitra un homme en costume malgré la simplicité du trait, car il a reconnu l'abstraction et en a déduit ce qu'elle représentait[2].

Il ne s'agit pas ici de démontrer la pertinence de l'analyse de Koster (j'en serais bien incapable de toute façon). Elle a toutefois le mérite de tenter de modéliser une manière dont nous comprenons les choses, aussi vais-je la tenir pour acquise.

Dans la même veine, nous pouvons avancer que si notre manière de percevoir le code est basée sur l'abstraction que nous en faisons, alors l'incompréhension n'est que le résultat de notre capacité à formuler cette abstraction, et ceci serait du au fait que nous serions incapable de retrouver des motifs connus dans ce code - ou plus exactement que nous serions incapable d'assimiler certaines portions du code à des motifs connus. Cet échec a plusieurs raisons : la première est que le code peut faire appel à des connaissances théoriques qui nous sont étrangères (par exemple, un algorithme de démodulation QPSK sera difficile à saisir pour quelqu'un qui n'a que peu de connaissances dans le domaine). La seconde est que le code peut utiliser des technologies que nous ne maitrisons pas : l'exemple le plus simple est le langage de programmation. Un programmeur Java a peu de chance de comprendre un code source écrit en CAML s’il ne connait pas ce langage et sa philosophie. Enfin, le code peut être lui même très difficile à déchiffrer - qu'importe le niveau de connaissance du programmeur, un code véritablement très mal écrit peut être complexe voire impossible à interpréter. Pour s'en convaincre, il suffit de regarder ce programme C[3]

main(){
  int x=3,n,m=2,*t,*a,*b=0;
  while(b?o:((*(t=b=(int*)malloc(o))=2),a=t+1,o))n=*b,n>=m?c:x%n?(int)b++:N);
}

On ne peut pas quantifier de manière précise le manque de connaissances théoriques ou technique. De plus, même si on pouvait ramener ces deux informations à une grandeur quelconque, on ne serait guère plus avancé : par essence, elles varient d'un programmeur à l'autre, elles sont par nature subjectives et non pas intrinsèque au code. Si notre but est de déterminer une métrique de lisibilité du code source, il est évident qu'il est impossible de les considérer - seule la complexité intrinsèque du code peut l'être.

Pour autant, est-ce possible de mesurer objectivement la difficulté à déchiffrer un code source ?

Des travaux intéressants ont déjà été menés sur ce sujet. Par exemple, Thomas McCabe a décrit en 1976 ce qu'il a appelé la complexité cyclomatique[4] d'une méthode ou d'une fonction. Bien que révélatrice d’un certain nombre de problème, cette métrique n'est pas pour autant une mesure de la lisibilité du code, mais une mesure de la complexité du flux des instructions : au final, elle donne le nombre de chemins possibles dans une fonction[5]. A un niveau plus haut, on trouve les métriques d'intelligibilité : ainsi, le logiciel Project Analyzer Entreprise Edition[6] définit une métrique de compréhension qui est en fait le regroupement de plusieurs métrique : LEN* (calcule la taille des identifiants), UNIQ (ratio nombre de noms uniques sur nombre de noms définis) ou encore MCOMM% (densité des commentaires). Ces mesures, bien que donnant différentes informations sur le code, ne peuvent toutefois être combinée pour obtenir au final une mesure globale de la lisibilité du programme.

Pour ce convaincre que bien qu'utiles ces métriques n'en sont pas moins différentes d'une métrique de lisibilité, il convient de se reporter à la taxonomie des mesures de qualité du SEI de Carneggie Mellon. Ces deux métriques (et un certain nombre d'autres métriques associées) ont été classée par le SEI de Carneggie Mellon dans la catégorie Quality Measure: Maintenance Measure, dans laquelle figure une autre catégorie : la lisibilité.

Si la lisibilité d'une prose est depuis longtemps un sujet d'étude qui a permit d'établir un certain nombre de formules empiriques basées sur des ensembles statistiques (index SMOG, test de lisibilité Flesch-Kincaid[7] ou encore la formule de lisibilité de Fry), force est de constater que peu de travaux on été entrepris pour transposer ces tests statistiques dans le monde de la programmation - la plupart des recherches s'orientant vers le domaine de la complexité du code.

Bien entendu, ces différentes formules ne sont pas directement adaptables à l'étude d'un code source. La plupart ne prennent en compte que des données simples comme le nombre de syllabes des mots, le nombre de mots, etc. Puisque lorsque nous programmons nous ne manipulons pas ce genre de quantités, il devient évident que ces formules ne peuvent pas être utilisées sans modification, mais il n'en reste pas moins qu'on peut intuitivement rapprocher un code source d'un texte en langage naturel. Que nous manque-t-il alors pour arriver à nos fins ? Deux choses, principalement.

En premier, il nous faut déterminer quels sont les composantes de l'équation - c'est à dire les groupes informations qui peuvent influer sur la lisibilité du texte. Plusieurs pistes d'offrent à nous :

  • l'étude des identifiants : lorsqu'il est trop long, un identifiant peut devenir difficile à déchiffrer. Trop court, il prend le risque de ne pas être assez descriptif. Le fait d'utiliser des séparateurs (première lettre de chaque mot en majuscule ou caractère souligné entre chaque mot par exemple) peut aider à la compréhension. L'utilisation de verbes dans un nom de fonction est une bonne chose, de même que l'utilisation de préfixes sémantiques (unités, etc.) ou structurels (la célèbre "notation hongroise"). Si dans le code on considère comme acquis des connaissances particulières sur les identifiants (par exemple px ou py pour des coordonnées en pixels sur une image), l'utilisation de ces connaissances peut elle aussi être un plus.
  • l'utilisation de notations idiomatiques : certaines notations sont plus aisées à comprendre que d'autres parce qu'elles sont largement répandues dans l'ensemble des programmeurs. Une notation équivalente mais plus rare peut alors provoquer des problèmes de compréhension. C'est le cas notamment de la célèbre "boucle infinie qui en fait n'est exécutée qu'une fois" dont j'ai parlé dans un billet antérieur : l'implémentation sous forme de boucle brouille les cartes par rapport à une implémentation plus simple utilisant les techniques RAII et une gestion correcte des exceptions.
  • l'utilisation de la grammaire : tous les langages offrent des possibilités grammaticales qui sont rarement utilisées. Lorsqu'elles le sont, elles peuvent alors poser un problème de compréhension - qui peut être assimilé à un manque de connaissances techniques, à ceci prêt qu'il est reconnu que l'utilisation de quelques fonctionnalités avancées dans certains langages augment de manière drastique le niveau de connaissance technique requises pour appréhender le code. C'est le cas par exemple dans le code suivant extrait d'un billet précédant, qui nécessite des connaissances pointues sur la manière dont le compilateur C++ fonctionne :
template <class T> class has_return_type
{
   template <class wrapped> class wrapper { };
   template <class u> static size_1& 
     test(wrapper<typename u::return_type>*);
   template <class u> static size_2& test(...);
public:
   static const bool value = (sizeof(test<T>(0)) == 1);
};
  • la forme du code : c'est évidemment un facteur à considérer, car si le fond peut rendre la compréhension d'un texte difficile, la forme est elle aussi très importante : non respect des règles d'indentation, utilisation aléatoire des parenthèses dans des expressions mathématiques complexes, une longueur de ligne excessive ou le manque de séparation des unités logiques dans le code peut être des obstacle à une lecture d'une code qui sans ça serait relativement aisée.

Ce dernier point est intéressant, parce qu'il couvre de nombreux cas particuliers dont la compréhension nous ouvre des portes prometteuses. Prenons par exemple le saut de ligne : dans l'effet, sauter une ligne permet de séparer des paragraphes. Quelle est alors la longueur optimale de ces paragraphes ? Une accolade fermante peut elle, à elle seule, constituer un paragraphe ? Autre cas : les déclarations de variables : doit-on les grouper ? Doit-on faire de ces groupes de déclarations des paragraphes ?

La réponse à ses questions n'est pas aussi simple qu'il n'y parait - chacun a sur ces questions un point de vue subjectif. Néanmoins nous cherchons à obtenir un point de vue objectif de la situation. C'est là notre second manque important : une fois déterminées les grandeurs que nous devons mesurer, il nous faut aussi pouvoir apprécier dans quelle mesure elles rentrent dans la composition d'une mesure de lisibilité. Ceci ne peut être fait que de manière statistique, et doit être basé sur une étude scientifique de grande échelle - une sorte de sondages/test à l'attention des programmeurs.

Lors de mes recherches sur le sujet, je n'ai pas trouvé d'indices qui montreraient qu'une telle étude a déjà été réalisée. Ce qui nous amène à cette conclusion : si notre but est de mesure la lisibilité d'un code source, il nous reste encore du travail avant même de commencer à implémenter quelque chose !

Notes

[1] A Theory of Fun for Game Design, R. Koster, Paraglyph Inc. ISBN: 1932111972

[2] ce personnage a été rapidement conçu grâce à South Park Studio v2

[3] ce code est issu du International Obfuscated C Code Contest, où il été élu Meilleur Programme Court. Il a été écrit par Jeff Weisberg, de l'Université de Rochester. Etes vous capable de découvrir ce qu'il fait juste en l'étudiant ?

[4] A Complexity Measure, T. McCabe, IEEE Trans. Software Eng. 2(4): 308-320

[5] la complexité cyclomatique est mesurée par le logiciel SourceMonitor (elle est alors simplement nommée Complexity)

[6] ce logiciel analyse le code Visual Basic et calcule entre autre un certain nombre de métriques associées à ce code

[7] ce test a été implémenté dans Microsoft Word afin de déterminer la complexité de votre texte ; de nombreux autres logiciels de traitement de textes l'implémentent. Le test Flesch-Kincaid est utilisé en référence par le gouvernement américain afin de déterminer le niveau de lisibilité des textes qu'il émet. Notez toutefois que ce test, ainsi que la plupart des autres, ciblent la langue anglaise et sont peu adaptés à la langue française qui contient davantages de mots courts. Ainsi, ce texte a un score FK de -51 environ, alors que la note est censée se situer entre 0 et 100 environ.

Ajouter un commentaire

Les commentaires peuvent être formatés en utilisant une syntaxe wiki simplifiée.

Fil des commentaires de ce billet