3. Les fonctions
Après cette quête, tu comprendras l'utilité des fonctions et tu sauras en créer
Objectifs
- Savoir identifier une responsabilité
- Maîtriser la syntaxe d'une signature de fonction
- Utiliser des paramètres
Ton usine fait faillite

Tu es employé dans une usine. Pendant que tu travailles sur la chaîne de montage, tu croises ton responsable apeuré, blanc de peur et stressé. Tu lui demandes ce qui ne va pas, car cela fait plusieurs mois qu'il est irascible et que cela te pèse.
Après quelques minutes de discussion, le voilà qui te révèle que l'usine va bientôt fermer. Ton responsable a toujours voulu maintenir un travail fait main où chaque employé connaît les détails du travail à fournir, mais aujourd'hui il est dépassé par les usines ayant tout automatisé. Il n'est plus compétitif ; ses temps de production sont trop longs et les produits fournis sont trop souvent livrés avec des défauts. Cela fait deux ans qu'il fait tourner son usine avec un prêt.
Toi, en grand connaisseur des automatismes, vas lui proposer une solution à même de lui remonter le moral : tu lui proposes d'automatiser tous les processus trop longs et de tabler sur des machines. Qu'importe si les employés savent ce qui se passe à l'intérieur de la machine, la productivité augmentera ainsi que la qualité des productions, et ton responsable et son usine n'auront plus de mouron à se faire. L’activité pourra même se diversifier une fois qu'il aura compris comment fonctionne les machines qu'il aura achetées.
Tu décides donc de lui expliquer en détail comment automatiser les processus et les principes cachés derrière ...
Une machinerie cachée

Lorsque tu fournis du matériel à une usine, par exemple du bois et du métal, l'objectif de l'usine est de transformer les matériaux en un bien : des meubles ou en un autre matériau. Quelle que soit la tâche qu'une usine a la responsabilité d’accomplir, elle prend un matériau et le transforme en quelque chose d'autre. Lorsque tu es un consommateur, tout ce qui s'est passé entre le moment où le.s matériau.x a/ont été récupéré.s par l'usine et celui où le.s matériau.x a/ont été transformé.s en un produit différent t'est caché. Tu ne sais pas ce qu'il s'est passé. Tu sais uniquement que l'usine a reçu du bois, du vernis et du métal et que les meubles en bois de ton appartement en sont sortis.
Eh bien, crois-le ou non, une fonction en informatique exerce la même responsabilité qu'une usine. Il s'agit d'un ensemble d'opérations qui prend des matériaux (paramètres), ou pas, et réalise une opération en fournissant ou non un résultat de sortie. Tu ne sais pas ce qu'il s'est passé entre le moment où tu lui as fourni des paramètres et le moment où elle termine son traitement. Tout cela doit rester identifié par un nom qui représente au mieux la responsabilité de la fonction. Les engrenages d'une fonction sont masqués, on ne peut pas les voir (à moins de lire son code, bien entendu).
Les programmes informatiques sont organisés, tout comme la chaîne de production d'une usine. Chaque engrenage a une utilité particulière et peut être utilisé dans un ou plusieurs cas. Chaque machine a une ou plusieurs responsabilités et un fonctionnement qui lui est propre. Un programme informatique est organisé d'une manière semblable : chaque fonction a une responsabilité et donc une utilité. Par exemple, la fonction avancerJoueur() permet, comme son nom semble l'indiquer, de faire avancer un joueur, mais on ne sait comment et d'ailleurs cela nous est égal, car on veut juste faire avancer ce joueur.
Les fonctions servent à abstraire au maximum afin d'avoir un code clair et lisible, maintenable et évolutif, séparé et compartimenté de manière que chaque partie du code puisse être comprise sans que le reste n’ait été lu. C'est très difficile et il faut, pour la plupart des développeurs, des années avant d'aborder et de comprendre sa responsabilité dans la qualité d'une prestation logicielle, alors mieux vaut que ces notions de qualité soient introduites tôt - avant que tu saches faire des trucs qui marchent - que tard - lorsque tu es déjà sur le marché du travail et que tu as pris tes habitudes.

Développeur est un métier où il faut constamment se remettre en question afin de rester compétitif, alors commence déjà avec tes boucles, tes conditions et tes fonctions et, lorsque tu auras réussi à pondre un code qui marche, relis-le ou fais-le relire afin de l'améliorer et/ou de corriger certaines parties qui pêchent. Si tu vois que le code est trop compliqué, le subdiviser en fonctions peut être une bonne approche.
Bon, assez parlé ! On attaque les fonctions ?
Une signature de fonction
![]()
A l'image de la signature d'une personne, une signature de fonction identifie la fonction.
Une signature décrit une fonction. Elle ne montre pas comment elle est implémentée, ni comment elle fonctionne, mais ce dont elle a besoin pour exécuter sa responsabilité. De même qu'une usine qui, pour fabriquer un produit, doit impérativement être pourvue de certains matériaux, une signature de fonction est la fiche descriptive de ce dont a besoin la fonction pour produire son résultat.
Alors, ça ressemble à quoi une signature de fonction ?
Commençons d'abord par une représentation plausible en pseudo code:
Fonction MangerDuPain: Paramètres kilogrammes en entier, deManièrePropre en booléen
Retourne un booléen
Intéressons-nous aux spécificités de ma fonction :
-
Elle a un nom :
MangerDuPain -
Elle prend en premier paramètre
kilogrammesqui représente le nombre de kilogrammes ingérés -
Elle prend un deuxième paramètre
deManièreProprequi spécifie si le pain doit être mangé proprement ou pas -
Elle retourne un booléen indiquant si l'action de manger du pain s'est bien déroulée
Que de nouveaux concepts ! Clarifions un peu tout ça.
La fonction a un nom. Son nom a la même utilité qu'un nom de variable : il sert à l'identifier et à y faire appel lorsque l'on a besoin d'elle. Le nom doit décrire de manière claire ce que réalise la fonction : sa responsabilité. Ainsi, on devine bien que la fonction MangerDuPain sert à manger du pain et pas des clous !
La fonction prend ou non des arguments (ou des paramètres). Ces paramètres alimentent la fonction en données, ils lui fournissent le nécessaire afin qu'elle puisse réaliser son opération. Tout comme un nom de fonction, les arguments d'une fonction doivent avoir des noms d'arguments explicites afin que l'on comprenne directement, en lisant la signature, de quoi il s'agit. C'est un impératif pour fournir du code propre. La présence d'arguments dans une fonction n'est cependant pas obligatoire et certaines fonctions n'ont pas besoin d'être alimentées en données afin de réaliser le travail qui leur est attribué.
La fonction retourne une valeur. En effet, tout comme une usine livre un produit fini, la fonction retourne un résultat.
Amusons-nous un peu avec du pseudo code et ma super fonction MangerDuPain:
Variable volumePainMangé est un entier initialisé à 0
Pour gramme allant de 0 à 100 * kilogrammes
Si JeNaiPlusFaim()
casser
FinSi
volumePainMangé = volumePainMangé + 10
OuvrirBouche()
MettreDansBouche(10)
Si deManiereProre est vrai
FermerBouche()
FinSi
Mâcher()
Avaler()
FinPour
retourner volumePainMangé
FinFonction
-
Je déclare une fonction
MangerDuPain -
Je lui spécifie deux paramètres : des
kilogrammesen valeur entière et un booléendeManièrePropre -
Je déclare une variable
volumePainMangé -
Si je n'ai plus faim, je quitte la boucle
-
Pour chaque dix grammes de pain :
-
J'attrape dix grammes de pain
-
J'ouvre ma bouche
-
Je mets le pain dans ma bouche
-
Si je suis propre et respecte mes convives
- Je ferme la bouche avant de mâcher
-
Je mâche
-
J'avale
-
Et voilà comment un être bien éduqué est censé manger du pain dans la plupart des cas.
Regardons un peu en détail ce qui caractérise ma fonction MangerDuPain:
-
Elle a un nom :
MangerDuPain -
Elle prend deux paramètres :
kilogrammesetdeManièrePropre -
Elle renvoie un entier qui représente la quantité de pain que j'ai mangée
Le pseudo code, c'est bien beau, mais je ne peux pas l'exécuter !
La fonction Main
Après tout ce que je viens de t'expliquer, tu seras ravi d'apprendre qu'en C#, les fonctions n'existent tout simplement pas !
Alors, pourquoi tu m'en parles ? Tu cherches à m'enfumer ?
Absolument pas ! C'est juste que, derrière les fonctions en C#, se cache un mécanisme un tantinet plus complexe. Cependant, tu n'as pas besoin d'en connaître les tenants et aboutissants pour le moment. Mais nous allons quand même étudier un bout de code dans lequel est écrit la première fonction que tu as manipulée : Main.
using System;
class MainProgram
{
public static void Main()
{
// Code la fonction Main
}
}
Main est ce qu'on appelle le point d'entrée de ton programme. C'est à cet endroit que l'exécution du code d'un exécutable va débuter, et nulle part ailleurs. Si tu supprimes ta fonction Main, ton compilateur se plaindra qu'il n'y a pas de point d'entrée (essaye, si tu ne me crois pas !).
Eh bien, savais-tu que la fonction Main a plusieurs signatures possibles ?
Je ne te fais plus confiance, là ! Tu m'as perdu !
Patience, tout cela va tomber sous le sens ...
Il existe deux signatures à la fonction Main. Une sans argument, et l'autre avec un argument. La version avec argument en prend un de type string[]. Le tableau que la fonction Main en arguments sont les arguments ligne de commande, c'est-à-dire des chaînes de caractères spécifiées lorsque l'on invoque l'exécutable, après son nom.
Ainsi le programme suivant :
using System;
public class MainProgram
{
public static void Main(string[] commandLineArguments)
{
Console.WriteLine("Hello, " + commandLineArguments[0] + " !");
}
}
Compilé et exécuté en tant que say_hello:
C:\Users\Wilder\Programs\SayHello\bin\Debug> .\say_hello myself
Hello, myself !
La signature de la fonction Main que l'on utilise est void Main(string[]):
-
voidest le type de retour : la fonction Main ne renvoie rien -
Mainest le nom de la fonction -
Les arguments sont spécifiés entre les parenthèses
Un argument a un type, tout comme le retour. Une fonction ne peut pas renvoyer un autre type que celui spécifié dans sa signature. De même pour ses arguments.
Ainsi, pour déclarer une fonction en C# qui retourne un entier, on écrit:
public static int double(int x)
{
return x * 2;
}
Il faut mettre la déclaration de la fonction au bon endroit, sinon le code ne compile pas. Par conséquent :
using System;
public class MainProgram
{
public static void Main(string[] commandLineArguments)
{
Console.WriteLine("Type an integer: ");
int integerToDouble = Convert.ToInt32(Console.ReadLine());
int doubledInteger = doubleInteger(integerToDouble);
Console.WriteLine(integerToDouble + " * 2 = " + doubledInteger);
}
public static int doubleInteger(int x)
{
return x * 2;
}
}
La fonction doit donc être déclarée dans le bloc, précédée par public class MainProgram. Nous verrons pourquoi dans les prochaines quêtes. Contente-toi simplement de t'en souvenir et nous verrons plus tard ce que cela veut dire (et pourquoi les fonctions en C# n'existent pas).
Une responsabilité par fonction
Dans l'idéal théorique, chaque fonction doit exécuter une et une seule responsabilité. La fonction peut en revanche être composée de plusieurs fonctions qui ont chacune une et une seule responsabilité. Ce principe, en qualité logicielle, se nomme SRP pour Single Responsibility Principle. Ce principe énonce qu'une fonction ne doit assumer qu'une seule responsabilité. Et que, si elle en a plusieurs, le code est mal écrit et la fonction doit être séparée en plusieurs responsabilités plus petites.
Les premières questions à se poser lorsque l'on se lance dans l'écriture d'une fonction sont:
-
Qu'est-ce que ma fonction va assumer comme responsabilité ?
-
A t-elle besoin d'autres fonctions qui assumeront d'autres responsabilités ?
-
Dans quel contexte ma fonction pourra-t-elle être réutilisée ?
Ces questions sont difficiles. Elles nécessitent un haut niveau d'abstraction quant au code et un certain recul sur les problématiques liées à l'implémentation d'un programme. Mais il est tout à fait possible d'y réfléchir sans avoir dix ans de bouteille en développement informatique !
Imaginons que mon travail soit de construire des barres de fer à partir de fer brut. Voici les responsabilités que j'identifie (même si je ne suis pas métallurgiste) :
-
Produire la barre de fer à partir du matériau brut
-
Faire entrer le fer brut en fusion
-
Mouler le fer en fusion
-
Le couler dans le moule
-
Fermer le moule le temps que le fer soit de nouveau à l'état solide
-
Ouvrir le moule
-
Glisser la barre de fer dans le réceptacle prévu pour la réception des barres terminées
-
Voilà ! Afin de Produire une barre de fer qui est une seule responsabilité, j'ai besoin de six autres responsabilités. Evidemment, si j'avais à le coder, je découperais le code en six fonctions pour commencer. Ensuite, petit à petit, je découperais encore et encore afin d'avoir un code de plus en plus modulaire. Et pourquoi pas suffisamment modulaire pour créer des figurines sympathiques en fer ? Faut avouer qu'on ne se tape pas beaucoup de barres !
Challenge
Une calculette
Réalise une calculette basique avec cinq fonctions :
-
Addqui additionne deux nombres flottants -
Subtractqui soustrait deux nombres flottants -
Multiplyqui multiplie deux nombres flottants -
Dividequi divise deux nombres flottants -
Moduloqui renvoie le reste d'une division de nombres entiers
Fais précéder chacune des fonctions du mot-clé static.
first operand: 3.5
second operand: 4.5
which operation you choose: add
3.5 + 4.5 = 9
first operand: 2.2
second operand: 2.2
which operation you choose: multiply
2.2 x 2.2 = 4.4
first operand: 3.33
second operand: 5.0
which operation you choose: dividee
Unknown operation "dividee"
first operand: 2.33
second operand: 2.0
which operation you choose: substract
2.33 - 2 = 0.33
Critères de validation
-
Un lien vers ta solution GitHub
-
Une boucle infinie permet à l'utilisateur d'utiliser la calculette plusieurs fois sans relancer le programme
-
Un message d'erreur s'affiche dans le cas où l'utilisateur rentre une mauvaise opération