J'ai récemment, et suite à un ensemble de discussions et de lectures d'articles de et avec des programmeurs d'une certaine expérience, été pris d'un doute. Ce doute porte sur la structure du code source d'un programme.

Avec l'expérience se forme des habitudes de construction et surtout des schémas mentaux de structure de programmes. Au premier coup d'œil, on repère les grandes lignes et un fonctionnement général.

Mais ces schémas se construisent différemment chez les différents lecteurs. Et rencontrer des structures différentes peut ralentir la lecture et sa compréhension, amenant parfois à se dire que l'on lit « du mauvais code ».

Dans ces différentes structures, l'une de ces variations se situe dans le découpage en fonctions (ou procédures, ou méthodes, peu importe). On peut, aux deux extrêmes, situer d'une part un programme comme constitué d'une seule fonction contenant l'intégralité du code, et de l'autre part comme un programme constitué exclusivement de fonctions atomiques (qui ne font qu'une et une seule chose indivisible).

La grande majorité des programmes se situe quelque part entre les deux.

Prenons deux exemples, écrits dans un langage pseudo-C.

Le premier est plutôt « in extenso »

void engine()
{
    /* Initialization */
    Screen screen;
    Window window(window);
    Surface surface(window);

    /* Loading of the objects*/
    Objects objects = loadScene(SCENE_PATH);

    /* Main loop */
    while (!finished)
    {
        Event event = window.getNextEvent();

        switch (event)
        {
            case EVENT_QUIT:
                finished = true;
                break;
            case ...
                ...
        }

        for (int i=0; i<len(objects); i++)
        {
            update(objects[i]);
            display(surface, objects[i]);
        }

        flush(surface)
    }

    /* Shutdown */
    shutdown(window);
    /* TODO: add a leak checker here */
}

Voici le second exemple « in brevo »

void engine()
{
    initialization();
    loading();
    main_loop();
    shutdown();
}

Il s'agit bien entendu du même programme.

Ma construction personnelle m'a amené à trouver comme évident qu'une bonne structure doit permettre une lecture, une compréhension et une navigation aisée et efficace dans un programme.

Dans cette optique, le second exemple me parle mieux. Je vois en quelques lignes la structure du programme. Je n'ai aucun détails sur comment cela fonctionne, mais je sais vers où me diriger si j'en ai besoin.

Lorsque je lis le premier programme, de nombreuses questions et commentaires viennent me polluer l'esprit. Des questions dont je n'ai pas besoin de connaître les réponses dans l'immédiat, et des commentaires qui me gênent dans ma lecture.

Par exemple :

  • peut-être avez-vous des commentaires sur les choix de style de codage, le placement des parenthèses ou des espaces. Peut-être être vous déjà fatigués, ou repoussés, par un style auquel vous n'êtes pas habitué. Dans ce cas, c'est mal parti. Dans ce cas, lire de grands ensembles de lignes augmente la fatigue.
  • La structure n'est pas forcément évidente. Des commentaires séparent les grandes parties de la fonction, mais ils sont distants les uns des autres. Dans un programme plus réaliste, cette fonction serait aussi beaucoup plus longue. Impossible de lire la structure d'un coup d'œil, il faut manipuler par défilement d'écran ou repliement des scopes.
  • les différentes parties n'ont pas le même niveau de détail. Particulièrement, la taille de la partie centrale de la boucle est déséquilibrée par rapport aux autres éléments.
  • est-ce que le TODO final se groupe avec le Shutdown ? Ou bien est-ce une partie supplémentaire ? De plus, à moins d'être spécifiquement en train d'ajouter ce « leak checker », ce commentaire me ralenti, je n'en ai pas besoin.

Cependant, d'autres programmeurs sont gênés par la seconde forme et lui opposent des arguments de compréhension :

  • comment communiquent ces parties ?
  • qu'est-ce qu'elles font réellement ?
  • quelle est l'implémentation ?
  • c'est beaucoup d'appels inutiles à du code qui n'est pas de la factorisation.

Et certainement d'autres.

De ces points, seul le premier et le dernier me touchent. Les deux centraux sont exactement ce que je considère gêner ma compréhension. Si je n'ai pas besoin des détails sur le moment, je veux pouvoir leur faire confiance.

Les appels inutiles

C'est vrai, il y a des sauts potentiellement générés et inutiles. Dans certains cas, le compilateur saura les supprimer et dans d'autres non. Dans le cas indiqué, vouloir supprimer quatre sauts uniques du programme n'est pas un bon argument. Dans des parties plus critiques du programme, cela se discute.

Je ne balaierai pas du revers de la main cette problématique comme bon nombre d'articles et de conférences qui disent : bah, les performances, ce n'est pas vraiment un soucis. D'une part, mon secteur (le jeu vidéo) ne peut pas tenir ce discours. D'autre part, il arrive qu'une construction d'un programme complexe se révèle très lent à cause de nombreux petits appels sans gravité localement. Il est alors très complexe de trouver quoi optimiser sans tout reprendre de zéro.

Je l'ai déjà vécu, c'est moche.

Ma ligne de conduite est : vérifier les performances du logiciel tout au long du développement, pas à la fin. Ne jamais inliner pas par défaut « au cas où ».

La communication

Je pense que le coeur de la dissension est là. En poussant la structuration et en cachant les mécanismes d'implémentation, il peut devenir plus compliqué pour le lecteur de suivre le flot, le déroulement, du programme.

En effet, si les appels de fonctions ont des effets de bords, le lecteur doit, en plus de tracer le déroulement général du programme, comprendre les chemins secondaires des données.

Lors de l'analyse du code, le lecteur construit une image mentale des manipulations de données et du flot du programme. Cette image mentale pour certains est plus simple lorsque tout est sous les yeux, dans une fonction in extenso. Pour d'autres l'image est plus simple si elle peut être découpée en parties fonctionnelles labellisées.

Et ces deux façons de penser semblent peu conciliables.

Et je le répète, elles se retrouvent toutes deux chez des programmeurs avec plus de deux décennies d'expérience de programmation.

Du coup, je doute.

Cela ne change pas mes choix de ce qui est le plus lisible aujourd'hui, mais je comprends que ces choix ne sont pas absolus.