Procédures et Fonctions

Ceci est la dernière partie « simple » avant de passer aux choses plus sérieuses.

Fonctions

Une application, surtout si elle est longue, a toutes les chances de devoir procéder aux mêmes traitements, ou à des traitements similaires, à plusieurs endroits de son déroulement. Par exemple, la saisie dune réponse par oui ou par non (et le contrôle quelle implique), peuvent être répétés dix fois à des moments différents de la même application, pour dix questions différentes.

La manière la plus évidente, mais aussi la moins habile, de programmer ce genre de choses, c’est bien entendu de répéter le code correspondant autant de fois que nécessaire. Apparemment, on ne se casse pas la tête : quand il faut que la machine interroge l’utilisateur, on recopie les lignes de codes voulues en ne changeant que le nécessaire, et roule Raoul. Mais en procédant de cette manière, la pire qui soit, on se prépare des lendemains qui déchantent…

D’abord, parce que si la structure d’un programme écrit de cette manière peut paraître simple, elle est en réalité inutilement lourdingue. Elle contient des répétitions, et pour peu que le programme soit joufflu, il peut devenir parfaitement illisible. Or, le fait d’être facilement modifiable donc lisible, y compris – et surtout – par ceux qui ne l’ont pas écrit est un critère essentiel pour un programme informatique ! Dès que l’on programme non pour soi-même, mais dans le cadre d’une organisation (entreprise ou autre), cette nécessité se fait sentir de manière aiguë. L’ignorer, c’est donc forcément grave.

En plus, à un autre niveau, une telle structure pose des problèmes considérables de maintenance : car en cas de modification du code, il va falloir traquer toutes les apparitions plus ou moins identiques de ce code pour faire convenablement la modification ! Et si l’on en oublie une, patatras, on a laissé un bug.

Il faut donc opter pour une autre stratégie, qui consiste à séparer ce traitement du corps du programme et à regrouper les instructions qui le composent en un module séparé. Il ne restera alors plus qu’à appeler ce groupe d’instructions (qui n’existe donc désormais quen un exemplaire unique) à chaque fois quon en a besoin. Ainsi, la lisibilité est assuré; le programme devient modulaire, et il suffit de faire une seule modification au bon endroit, pour que cette modification prenne effet dans la totalité de lapplication.

Le corps du programme sappelle alors la procédure principale, et ces groupes dinstructions auxquels on a recours sappellent des fonctions et des sous-procédures (nous verrons un peu plus loin la différence entre ces deux termes).

Reprenons un exemple de question à laquelle lutilisateur doit répondre par oui ou par non.

...
PRINT "Etes-vous marié ?"
Rep1 ← ""
WHILE Rep1 <> "Oui" AND Rep1 <> "Non"
  PRINT "Tapez Oui ou Non"
  INPUT Rep1
ENDWHILE
...
PRINT "Avez-vous des enfants ?"
Rep2 ← ""
WHILE Rep2 <> "Oui" AND Rep2 <> "Non"
  PRINT "Tapez Oui ou Non"
  INPUT Rep2
ENDWHILE
...

On le voit bien, il y a là une répétition quasi identique du traitement à accomplir. A chaque fois, on demande une réponse par Oui ou Non, avec contrôle de saisie. La seule chose qui change, c’est l’intitulé de la question, et le nom de la variable dans laquelle on range la réponse. Alors, il doit bien y avoir un truc.

La solution, on vient de le voir, consiste à isoler les instructions demandant une réponse par Oui ou Non, et à appeler ces instructions à chaque fois que nécessaire. Ainsi, on évite les répétitions inutiles, et on a découpé notre problème en petits morceaux autonomes.

Nous allons donc créer une fonction dont le rôle sera de renvoyer la réponse (oui ou non) de l’utilisateur.

FUNCTION RepOuiNon() : TEXT
    Truc ← ""
    WHILE Truc <> "Oui" AND Truc <> "Non"
       PRINT "Tapez Oui ou Non"
       INPUT Truc
    ENDWHILE
    RETURN Truc
ENDFUNCTION

On remarque au passage lapparition dun nouveau mot-clé : RETURN , qui indique quelle valeur doit prendre la fonction lorsqu’elle est utilisée par le programme. Cette valeur renvoyée par la fonction (ici, la valeur de la variable Truc) est en quelque sorte contenue dans le nom de la fonction lui-même.

Une fonction s’écrit toujours en-dehors de la procédure principale. Selon les langages, cela peut prendre différentes formes. Mais ce qu’il faut comprendre, c’est que ces quelques lignes de codes sont en quelque sorte des satellites, qui existent en dehors du traitement lui-même. Simplement, elles sont à sa disposition, et il pourra y faire appel chaque fois que nécessaire. Si l’on reprend notre exemple, une fois notre fonction RepOuiNon écrite, le programme principal comprendra les lignes:

...
PRINT "Etes-vous marié ?"
Rep1 ← RepOuiNon()
...
PRINT "Avez-vous des enfants ?"
Rep2 ← RepOuiNon()
...

Et le tour est joué ! On a ainsi évité les répétitions inutiles, et si d’aventure, il y avait un bug dans notre contrôle de saisie, il suffirait de faire une seule correction dans la fonction RepOuiNon pour que ce bug soit éliminé de toute l’application. Elle n’est pas belle, la vie ?

Toutefois, les plus sagaces d’entre vous auront remarqué, tant dans le titre de la fonction que dans chacun des appels, la présence de parenthèses. Celles-ci, dès qu’on déclare ou qu’on appelle une fonction, sont obligatoires. Et si vous avez bien compris tout ce qui précède, vous devez avoir une petite idée de ce qu’on va pouvoir mettre dedans…

Passage d’arguments

Reprenons lexemple qui précède et analysons-le. On écrit un message à l’écran, puis on appelle la fonction RepOuiNon pour poser une question ; puis, un peu plus loin, on écrit un autre message à l’écran, et on appelle de nouveau la fonction pour poser la même question, etc. Cest une démarche acceptable, mais qui peut encore être amélioré: puisque avant chaque question, on doit écrire un message, autant que cette écriture du message figure directement dans la fonction appelée. Cela implique deux choses :

  • lorsqu’on appelle la fonction, on doit lui préciser quel message elle doit afficher avant de lire la réponse
  • la fonction doit être « prévenue» qu’elle recevra un message, et être capable de le récupérer pour l’afficher

En langage algorithmique, on dira que le message devient un argument (ou un paramètre) de la fonction. Cela n’est certes pas une découverte pour vous : nous avons longuement utilisé les arguments à propos des fonctions prédéfinies. Eh bien, quitte à construire nos propres fonctions, nous pouvons donc construire nos propres arguments.  Voilà comment laffaire se présente…

La fonction sera dorénavant déclarée comme suit :

FUNCTION RepOuiNon(Msg : TEXT) : CHAR
    PRINT Msg
    Truc ← ""
    WHILE Truc <> "Oui" AND Truc <> "Non"
        PRINT "Tapez Oui ou Non"
        INPUT Truc
    ENDWHILE
    RETURN Truc
ENDFUNCTION

Il y a donc maintenant entre les parenthèses une variable, Msg, dont on précise le type, et qui signale à la fonction quun argument doit lui être envoyé à chaque appel. Quant à ces appels, justement, ils se simplifieront encore dans la procédure principale, pour devenir :

...
Rep1 ← RepOuiNon("Etes-vous marié ?")
...
Rep2 ← RepOuiNon("Avez-vous des enfants ?")
...

Et voilà le travail.

Une remarque importante : là, on n’a passé quun seul argument en entrée. Mais bien entendu, on peut en passer autant quon veut, et créer des fonctions avec deux, trois, quatre, etc. arguments ; Simplement, il faut éviter d’être gourmands, et il suffit de passer ce dont on en a besoin, ni plus, ni moins !

Dans le cas que l’on vient de voir, le passage d’un argument à la fonction était élégant, mais pas indispensable. La preuve, cela marchait déjà très bien avec la première version. Mais on peut imaginer des situations où il faut absolument concevoir la fonction de sorte qu’on doive lui transmettre un certain nombre d’arguments si l’on veut qu’elle puisse remplir sa tâche. Prenons, par exemple, toutes les fonctions qui vont effectuer des calculs. Que ceux-ci soient simples ou compliqués, il va bien falloir envoyer à la fonction les valeurs grâce auxquelles elle sera censé produire son résultat (pensez tout bêtement à une fonction sur le modèle d’Excel, telle que celle qui doit calculer une somme ou une moyenne). C’est également vrai des fonctions qui traiteront des chaînes de caractères. Bref, dans 99% des cas, lorsqu’on créera une fonction, celle-ci devra comporter des arguments.

Deux mots sur l’analyse fonctionnelle

Comme souvent en algorithmique, si l’on s’en tient à la manière dont marche l’outil, tout cela n’est en réalité pas très compliqué. Les fonctions personnalisées se déduisent très logiquement de la manière nous nous avons déjà expérimenté les fonctions prédéfinies.

Le plus difficile, mais aussi le plus important, c’est d’acquérir le réflexe de constituer systématiquement les fonctions adéquates quand on doit traiter un problème donné, et de flairer la bonne manière de découper son algorithme en différentes fonctions pour le rendre léger, lisible et performant.

Le jargon consacré parle d’ailleurs à ce sujet de factorisation du code : c’est une manière de parler reprise des matheux, qui « factorisent » un calcul, c’est-à-dire qui en regroupent les éléments communs pour éviter qu’ils ne se répètent. Cette factorisation doit, tant qu’à faire, être réalisée avant de rédiger le programme : il est toujours mieux de concevoir les choses directement dans leur meilleur état final possible. Mais même quand on s’est fait avoir, et qu’on a laissé passer des éléments de code répétitifs, il faut toujours les factoriser, c’est-à-dire les regrouper en fonctions, et ne pas laisser des redondances.

La phase de conception d’une application qui suit l’analyse et qui précède l’algorithmique proprement dite, et qui se préoccupe donc du découpage en modules du code s’appelle l’analyse fonctionnelle d’un problème. C’est une phase qu’il ne faut surtout pas omettre ! Donc, je répète, pour concevoir une application :

On identifie le problème à traiter, en inventoriant les fonctionnalités nécessaires, les tenants et les aboutissants, les règles explicites ou implicites, les cas tordus, etc. C’est l’analyse.

On procède à un découpage de l’application entre une procédure qui jouera le rôle de chef d’orchestre, ou de donneur d’ordre, et des modules périphériques (fonctions ou sous-procédures) qui joueront le rôle de sous-traitants spécialisés. C’est l’analyse fonctionnelle.

On détaille l’enchaînement logique des traitements de chaque (sous-)procédure ou fonction : c’est l’algorithmique.

On procède sur machine à l’écriture (et au test) de chaque module dans le langage voulu : c’est le codage, ou la programmation proprement dite.

Print Friendly, PDF & Email