Je me permets de donner quelques éléments pour orienter le débat. Il faut savoir que la problématique a étè étudiée par des psychologues cognitifs, et plus précisément par des spécialistes de Psychology Of Programming. Pour ce faire, diverses expériences ont étés effectuées.
Expériences
On peut notamment citer celle effectuée par (Boehm-Davis + al., 1992). Dans celle-ci, les expérimentateurs avaient préparés 3 programmes. Chacun de ces 3 programmes était déclinés en trois versions identiques à un détail prêt :
- un de ces 3 programmes était écrit d'une manière monolithique : un bon gros main des familles comme on en fait plus ;
- un découpé en fonctions assez longues ;
- un découpé en suivant les bonnes pratiques OO, avec pleins de petites fonctions partout, de l'héritage et des tas d'autres trucs dans le genre.
Bilan : les programmes OO, avec des fonctions courtes, étaient perçus par les programmeurs comme difficiles à lire et à comprendre. Les étudiants et professionnels ayant servis de cobayes avaient aussi mis plus de temps pour effectuer divers changements dans le programme OO que dans les autres programmes (avec un gros main et des fonctions longues).
Les études suivantes : El-Emam et al., 1999; El-Emam et al., 2001b, ont montrées que la majorité des bugs et erreurs dans une application se situaient justement aux endroits où l'on trouvait de l'export coupling. A savoir, quand un truc défini dans un module/classe est réutilisé ailleurs. Et pire : plus le chunk en question était réutilisé, pire c'était. Et les appels de fonction sont justement un cas d'export coupling.
Autre exemple : les travaux de Wang sur les poids cognitifs de différentes structures de contrôle usuelles. Ces travaux supposent une différence de compréhension entre processus cognitif inconscients (lecture linéaire du code), méta-cognitifs (sauts simples, conditions, appels de fonctions), et processus de haut niveau (boucles, récursion, etc). La recherche sur les poids cognitifs (cognitives weight) se base sur ces concepts, et tend à montrer que les appels de fonctions auraient un poids cognitif plus élevé que la lecture linéaire de code.
Théories
Les raisons à cet état de fait sont dorénavant bien connues, et ont notamment étè étudiées par (Cant, al., 1995). Pour les auteurs, la compréhension d'un code source dépend de deux facteurs : le chunking, et le tracing. Commençons par le chunking.
Chunking
Comme vous le savez tous, notre mémoire est décomposées en plusieurs sous-mémoires : des mémoires sensorielles, des mémoires à court terme, et des mémoires à long terme. Nos mémoire à court terme ont une capacité finie : elles peuvent retenir un nombre limité de connaissances de base, des chunks. Si jamais on essaye de traiter plus d’éléments que nos mémoires à courts terme ne peuvent en contenir, les performances cognitives s'effondrent et, le taux d'erreur augmente fortement.
Les développeurs sont soumis à ces contraintes : un code source lisible est un code source facilement découpable en chunks. Pour découper un code source, les développeurs se basent sur divers indices : découpage en paragraphe, indentation, etc ; pour reconnaitre des chunks et/ou des morceaux de code familiers.
Pour comprendre un code, un développeur doit comprendre :
- les chunks en eux-même ;
- les chunks dont le chunk dépend (exemple : fonction utilisée dans un chunk), et récursivement sur ces chunks ;
- et comment les chunks sont reliés entre eux, et les dépendances qui en suivent.
Tracing
Mais ce chunking du code peut être perturbé par divers effets. On peut notamment citer les effets de tracing. Le tracing, c'est quand un développeur/lecteur de texte doit interrompre sa lecture pour la reprendre à un autre endroit assez éloigné du code : dans un autre fichier, ou ailleurs. Il doit alors traverser le programme en cours pour repérer les chunks pertinents.
Ce tracing a deux effets :
- il impose un cognitive switch : l'attention est détournée dans un autre but, ce qui empêche le maintien du contenu des mémoires à court terme, et cause un effet d’interférence ;
- elle donne le champ libre au fan-effect, qui nous dit que plus un chunk est associé à d'autres chunks, plus celui-ci sera difficile à comprendre ou à mémoriser ;
- elle augmente le nombre d'associations non-inconscientes entre les chunks ;
Plus un code est découpé en fonctions, plus celui-ci a un tracing important. Par contre, plus la lecture du code est linéaire, sans sauts, moins il y aura de tracing. L'effet sur la charge cognitive est de plus en faveur d'un code linéaire : n'oubliez pas que lire du code linéaire ne demande pas de mémoriser consciemment les associations entre morceaux de code, contrairement aux appels de fonctions qui imposent des associations méta-cognitives (cf travaux de Wang cités plus haut).
Bilan
Tout ces éléments vont clairement dans le sens d'un découplage entre compréhension locale d'un code source, et compréhension globale. Ce qui a étè mentionné pas pas mal d'intervenants.
Bilan : si vous voulez écrire un code source, n'utilisez pas les fonctions pour le chunker. Un bon découpage se base plus sur le paragraphage, sur l'indentation, sur l'aspect extérieur et syntaxique du code qu'autre chose. Les fonctions courtes ne font que vendre du chunking contre du tracing. Il faut donc privilégier des fonctions assez longues, pour limiter la survenue d'effets de tracing au maximum des possibilités.
Autres
Pour ceux intéressés par le sujet, sachez que les fonctions ne sont pas les seules entités programmatiques qui subissent les effets du tracing. L'héritage est aussi fortement touché. Pour ceux qui s'intéresse au sujet, je recommande la lecture de l'article "Validating Object-Oriented Design Metrics on a Commercial Java Application", par Daniela Glasberg et al.
Partager