TD - Préparation

Structure hiérarchique d’un programme

La problématique

Voici des situations qui doivent attirer votre attention:

  • Duplication de code : plusieurs lignes similaires avec peu de différences

  • Du code gérant des détails se trouvant au même niveau que du code gérant des aspects plus généraux

  • Des variables globales

  • Des noms de variables/fonctions vagues

Comment mieux structurer ?

  • Mettre en place des fonctions: les fonctions sont utiles pour la lisibilité, la structuration et pour garantir une certaine forme d’indépendance entre les différentes parties du programme.

  • Utiliser des structures/classes: cela permet de regrouper des données et des fonctions au sein d’une même entité

Schéma d’une hiérarchie d’appels de fonctions

Les mauvais programmes partagent souvent le même défaut : un manque de structure. Au lieu d’être clairement séparées, les différentes étapes de traitement s’entremêlent, formant ce qu’on appelle du « code spaghetti » :

../_images/spaghetti.png

Structure hiérarchique du code : chaque bloc du schéma correspond à une fonction. La fonction principale, située en haut, pilote l’exécution en appelant d’autres fonctions placées à des niveaux inférieurs. Cette organisation reflète la structure logique du programme et facilite sa compréhension.

Par exemple, dans le ploc principal, on peut trouver un traitement assez général comme : fusionner plusieurs PDF, lire une vidéo, editer un fichier excel… En bas de la hiérarchie, on trouve plutôt des fonctions effectuant un traitement précis et basique comme: dessiner un cercle, vider une liste, calculer la moyenne d’une liste…

Identifier une hiérarchie d’appels de fonctions

Les mauvais critères

Voici quelques « mauvais critères » à éviter lorsque l’on chercher à structurer un programme :

  • Exactitude du traitement : un code peut être correct sans pour autant être bien structuré. Le fait que « ça fonctionne » n’est pas un critère de bonne structuration.

  • Longueur du code : Une fonction plus longue n’est pas forcément plus « haute » dans la hiérarchie. Le nombre de lignes n’est pas un indicateur pertinent.

  • Une unique hiérarchie : non ! Il existe toujours plusieurs façons de structurer un programme et cela dépend du contexte: cloud/local, interface texte ou graphique, jeu MMO ou solo…

Vocabulaire

Nous appelons sous-fonction une fonction appelée par une autre fonction. Dans le schéma des hiérarchies, son bloc se situe au-dessous d’un autre bloc. Les sous-fonctions d’une même fonction s’appellent des fonctions voisines.

../_images/ens.png

Principe 1 : les sous-fonctions traitent des sous-thématiques

Les fonctions voisines traitent chacune une sous-thématique de la thématique de la fonction appelante.

Avertissement

Si le traitement d’une sous-fonction est sans rapport avec la thématique de sa fonction appelante, alors cette fonction n’est pas correctement positionnée, il y a un problème de conception.

Par exemple, si vous trouvez une fonction de gestion des déplacements appelée par la fonction d’affichage, même si le programme fonctionne correctement, vous pouvez considérer que la conception n’est pas du tout adéquate à ce niveau.

Principe 2 : périmètres similaires pour des fonctions voisines

Respecter le principe numéro 1 ne suffit pas à créer une hiérarchie correcte. Pour cela, il faut aussi tenir compte du principe numéro 2 : des fonctions voisines doivent avoir un périmètre similaire.

Ce principe précise que deux sous-fonctions doivent avoir un périmètre similaire. Par exemple, si en dessous du bloc de la gestion du personnage, vous trouvez les blocs : gestion des déplacements, gestion des tirs, gestion des collisions puis un bloc intitulé « gestion de la clef », vous devez détecter un problème.

En effet, le bloc gestion des déplacements traite beaucoup d’aspects comme les touches du clavier, la collision avec les murs ou la montée des échelles et des escaliers. La gestion des collisions détecte le contact entre le héros et les monstres ou les pièges ou encore les bonus… Le bloc intitulé gestion de la clef s’avère avoir un périmètre très réduit par rapport à ses blocs voisins. Cela sous-entend que sa place est incorrecte. Le mauvais réflexe à ce niveau consiste à chercher à déplacer la gestion de la clef sous un bloc existant. On se retrouve alors à essayer chaque possibilité en recherchant la moins inadéquate. Ce n’est pas la bonne approche !

Pour réfléchir, il faut plutôt essayer d’imaginer le bloc qui n’existe pas encore. Dans notre exemple, le bloc absent correspond à la gestion de l’inventaire du héros. Ce bloc apparaîtrait naturellement si dans le scénario du jeu on ajoutait d’autres objets à collecter comme des gourdes d’eau, des potions de soins ou des pièces d’argent. En créant ce nouveau bloc pour gérer l’inventaire, nous avons maintenant un bloc de périmètre équivalent aux périmètres des blocs voisins. Ainsi, le bloc gestion de la clef se rattache correctement à lui.

Exercice 1

Comment identifier la place exacte de chaque fonction dans la hiérarchie ? C’est une question très difficile. Une aide peut être d’utiliser le principe n°1 et n°2. Prenons l’exemple d’un programme contenant les fonctions suivantes :

  • Gestion du freinage

  • Allumage du voyant réservoir vide

  • Gestion des vitesses

  • Gestion des airbags

  • Gestion des informations conducteur

  • Vérification de l’alimentation des airbags

Supposons que notre programmeur débutant, nommé Tom, construise cette première version :

../_images/ex1.png

Tom a mis en place une fonction principale, NavigationVoiture(), tenant le rôle de chef d’orchestre pour toutes les autres fonctions, c’est un bon début. Poursuivons l’analyse de ce schéma. Nous remarquons que toutes les autres fonctions se situent au même niveau dans la hiérarchie. Pourtant, les fonctions de gestion avec des périmètres très larges (vitesse, airbags et freinage) se trouve au même niveau que d’autres fonctions plus spécialisées comme l’allumage du voyant réservoir. Le principe n°2 nous rappelle que les fonctions voisines doivent avoir un périmètre similaire. Nous allons donc déplacer certains blocs sous d’autres. Pour les placer correctement, nous utilisons le principe n°1 : la thématique d’une fonction doit s’inclure dans la thématique de la fonction appelante. Ainsi, l’appel de la fonction d’allumage du voyant réservoir devrait appartenir à la fonction de gestion des informations conducteur et son bloc devrait se situer sous le bloc de cette fonction. D’autre part, la fonction de vérification de l’alimentation de l’airbag semble être une action appartenant au périmètre de la gestion des airbags et sa place devrait se situer plutôt à l’intérieur de ce traitement.

Réfléchissez à comment corriger cette hiérarchie. Après cela, vous pouvez consulter la correction correction.

Exercice 2

Identifier une fonction consiste à identifier une tâche ou un traitement dans le programme. Identifier le placement d’une fonction dans une hiérarchie s’avère bien plus difficile. Nous allons traiter maintenant un cas pratique : prenons l’exemple d’un jeu de labyrinthe où un héros doit trouver un coffre au trésor en évitant des momies. Nous savons que dans ce jeu apparaît un écran Game Over et un écran d’accueil. Nous pouvons rapidement identifier des fonctions importantes à mettre en place :

  • Gestion de l’écran d’accueil

  • Gestion de l’écran de fin

  • Déplacement du héros

  • Déplacement des momies

  • Gestion des collisions entre le héros et les momies

  • Ouverture du coffre

  • Affichage du labyrinthe et des personnages

La structuration basique qui vient à l’esprit immédiatement est la suivante :

../_images/ex2.png

Il faut repenser cette conception pour que le schéma corresponde à une hiérarchie occupant plusieurs niveaux. Ce qui nous amène à la question importante : quels blocs vont occuper le premier niveau ?

Examinons les thématiques des différents blocs. Le bloc du déplacement des momies, le bloc de déplacement du héros et le bloc de gestion du coffre ont des thématiques qui s’incluent dans la gestion de la partie. Le bloc écran d’accueil et le bloc Game Over sont indépendants et probablement voisins. Cette première analyse nous permet d’identifier trois groupes :

  • Ecran d’accueil

  • Ecran de fin

  • Gestion de partie : déplacements, collisions et affichage

Comme on passe plus de temps dans le jeu que sur l’écran d’accueil ou sur l’écran de fin, on peut avoir tendance à mettre le bloc de la Gestion de partie en haut de la hiérarchie, mais alors que faire des deux blocs Ecran d’accueil et Ecran de fin ? Si on oublie le critère du temps passé, nous avons en fait trois niveaux équivalents: Ecran d’accueil, Ecran de fin et Ecran de jeu. Ainsi, nous avons trouvé une thématique commune correspondant à leur bloc supérieur : la gestion des différents écrans.

Réfléchissez à comment corriger cette hiérarchie. Après cela, vous pouvez consulter un exemple de correction.