Document ID: /fineweb-2-swissfilter-quality_10-filterrobots/filtered/07081.jsonl.gz/7

Capsule : sous-programme, procédure et fonction
Introduction
Du point de vue du programmeur, un programme est une entité qui porte un nom et qui est constitué d’une séquence d’instructions écrite avec un langage de programmation. Ces instructions décrivent à la fois l’algorithme, c’est-à-dire la méthode qui permet de réaliser le traitement, et la forme des données d’entrée et des données de sortie. Les mots qui constituent le vocabulaire de base du langage de programmation permettent de décrire n’importe quel traitement et est donc peu spécifique. Un programme qui ne serait écrit qu’avec ce vocabulaire de base serait, de fait, difficile à comprendre. L’une des solutions à ce problème est l’utilisation de commentaire. Il est possible d’ajouter dans le code du programme, des commentaires en français ou dans n’importe quelle autre langue naturelle, pour expliquer à quoi sert telle instruction ou tel groupe d’instructions. Toutefois, lorsque les commentaires ne font que répéter en français, ce que disent les instructions en code, cette solution souffre des problèmes liés à la redondance des données. En particulier, il existe le risque que le code change sans que les commentaires correspondants soient mis à jour. Si les commentaires et le code ne disent plus la même chose, le code devient alors encore plus difficile à comprendre.
Une meilleure solution consiste à ajouter un vocabulaire plus spécifique au langage de programmation et l’une des manières de le faire est de définir des sous‑programmes, l’objet de cette capsule de théorie. Comme un programme, un sous‑programme a un nom parlant qui indique clairement ce qu’il fait, et dispose d’un moyen de recevoir et de renvoyer des données. Contrairement à un programme, l’exécution d’un sous‑programme n’est pas lancée directement par un utilisateur final, mais par un appel de sous‑programme dans le code source d’un programme.
Dans les sections qui suivent, nous commençons par examiner les notions de module, d’espace de noms et de bibliothèque en général et de bibliothèque standard en particulier. Comme nous le verrons, la bibliothèque standard de Java nous est déjà en partie connue. Nous définissons ensuite de manière plus détaillée la notion de sous‑programme avant de nous intéresser à leur réalisation en Java.
Notion de module et d’espace de noms
En programmation, un module peut être défini comme une unité d’organisation du code, c’est-à-dire un moyen de regrouper différentes parties du code « qui vont bien ensemble ». La notion de module ne renvoie donc pas à une chose en particulier, mais à n’importe quel moyen permettant d’organiser du code. Dans le cadre de ce cours, nous parlerons de module pour désigner un moyen qui permet de regrouper un ensemble de sous‑programmes et de nommer cet ensemble; en Java, il s’agit d’une classe. Par exemple, la classe
Math est un module qui regroupe les fonctions mathématiques d’usage courant comme les fonctions
sin,
cos,
sqrt,
pow ou encore
round.
En Java, le nom d’une classe est généralement le nom d’une chose sans article (un groupe nominal sans déterminant) et s’écrit en « upper camel case » sans accents. Par exemple, une classe qui représente une « chouette messagère » se nomme
ChouetteMessagere ou, en anglais,
MessengerOwl (l’absence d’accent donne un avantage à l’anglais). Une classe doit se trouver dans un fichier dont l’extension est
.java dont le correspond exactement à celui de la classe. Par exemple, si le nom de la classe est
App alors cette classe doit impérativement se trouver dans un fichier
App.java, si le nom de la classe est
MessengerOwl alors cette classe doit impérativement se trouver dans un fichier
MessengerOwl.java. Il est important de noter que les majuscules et les minuscules sont significatives.
Le nom d’une classe doit être unique. Toutefois, lorsqu’il y beaucoup de classes et en particulier quand on utilise à des classes écrites par des tiers, il peut devenir difficile de trouver un nom de classe qui n’a jamais été utilisé. La manière usuelle d’éviter ce problème en informatique consiste à définir un espace de noms différent pour chaque partie. En Java, on réalise cela avec des packages. Comme une classe permet de rassembler des sous‑programmes, un package qui permet de rassembler des classes. De plus, les packages peuvent être organisés sous une forme hiérarchique; un package peut contenir d’autres packages. Concrètement, un package correspond à un répertoire qui peut contenir des fichiers .java (classe) et d’autres répertoires (packages). Le nom complet d’une classe (fully qualified name) se compose de la juxtaposition du nom de chaque package de la hiérarchie et du nom de la classe séparés par un point. Par exemple,
ch.epai.ict.m404.exercice1.App identifie la classe
App qui se trouve dans le package
exercice1 qui se trouve dans le package
m404 qui se trouve dans le package
ict, etc. Le graphique de la figure 1 illustre la structure de répertoires correspondante.
Le code de la figure 2 montre la définition de la classe
App qui doit obligatoirement se trouver dans un fichier nommé
App.java (voir plus haut). La première ligne d’un fichier
.java est toujours la déclaration du package qui doit correspondre précisément à structure de répertoire dans lequel le fichier se trouve. La compilation est impossible si le nom du fichier et la structure de répertoire ne correspondent pas au nom de la classe et à la déclaration du package.
1 2 3 4 5 6 7 8 9 10 11 // Déclaration du package package ch.epai.ict.m404.exercice1; // Définition de la classe App public class App { // Ajouter la procédure main s’il s’agit de la classe principale // Ajouter les sous-programmes du module }
Bibliothèse et bibliothèque standard
Lorsque des modules sont utilisables dans plusieurs programmes différents, on peut rassembler ces modules dans des bibliothèques (library, attention, faux-amis). En Java, une bibliothèque prend généralement la forme d’un fichier JAR. Un fichier JAR (Java archive) est un fichier dont l’extension est « .jar » et qui contient une archive au format ZIP. L’archive contient une structure de répertoire correspondant aux packages et des fichiers « .class » qui correspondent aux classes compilées.
Une bibliothèque standard est une bibliothèque mise à disposition directement par le langage de programmation. En Java, la bibliothèque standard s’appelle Java Class Library ou JCL qui contient environ 4000 classes. Parmi celles-ci, on peut mentionner la class
System qui permet d’avoir accès à l’entrée et à la sortie standard (
System.in et
System.out), la class
Math dont nous avons déjà parlé, la classe
String qui représente une chaîne de caractère, ou encore la classe
ArrayList qui représente une liste d’objet.
Notion de sous‑programme
La notion de sous‑programme renvoie à une construction d’un langage de programmation qui permet de donner un nom à une séquence d’instructions qui constitue une partie d’un programme. Un sous‑programme peut soit avoir un effet, c’est-à-dire que l’exécution du sous‑programme modifie l’état du système, soit produire une valeur. Dans le premier cas, on parle de procédure et, dans le second, de fonction. L’appel d’une procédure est une instruction et l’appel d’un fonction est une expression. Un sous‑programme peut avoir des paramètres qui permettent à l’appelant de lui passer des données.
On distingue l’appel et la définition d’un sous-programme. Avant de pouvoir appeler (utiliser) un sous‑programme, celui-ci doit être défini. Définir un sous‑programme consiste à déclarer sa signature composée d’un nom et d’une liste des paramètres formels (éventuellement vide), et à lui associer les instructions qui constituent le corps du sous‑programme.
En Java, le nom d’un sous‑programme s’écrit en « lower camel case » et se compose généralement d’un verbe à l’infinitif et d’un complément (groupe verbal). Par exemple, le groupe verbal « résoudre l’équation du second degré » s’écrit
resoudreEquationDuSecondDegre ou, en anglais,
solveSecondDegreeEquation. Là encore, l’anglais a l’avantage par rapport au français.
Dans le diagramme de la figure 3 qui décrit la définition d’un sous‑programme en Java, le symbole non terminal « FormalParameters » fait référence à la déclaration des paramètres formels dont la syntaxe est décrite par la figure 4.
Les paramètres permettent à un sous‑programme appelant (p. ex. la procédure principale) de passer des données au sous‑programme appelé. Du point de vue du sous‑programme, les paramètres formels sont en tout point identiques à des variables locales dont la valeur est définie au moment de l’appel.
Le dernier symbole non terminal (InstructionBlock) du diagramme de la figure 3 représente le corps du sous‑programme. Il s’agit d’une séquence d’instructions entre des accolades comme le montre le diagramme syntaxique de la figure 5.
Signature d’un sous‑programme, encapsulation et abstraction
La signature d’un sous‑programme est constituée du type de retour (dans le cas d’une fonction), du nom du sous‑programme et de la liste des paramètres formels. Dans le code de la figure 6, la signature de la procédure principale est :
static void main(String[] args).
L’encapsulation désigne le fait qu’un sous‑programme permet d’isoler (encapsuler) une séquence d’instruction pour se concentrer sur ce qu’elle doit faire sans avoir à se soucier de la manière dont elle sera utilisée. Le terme fait référence à l’idée que la fonction est une capsule — dans le sens d’un emballage — dans laquelle est enfermée l’implémentation.
L’abstraction désigne à la fois l’action de construire une représentation d’une chose en ne considérant que certaines de ses propriétés, et le résultat de cette action (la représentation obtenue). Dans le cas d’un sous‑programme, la propriété retenue est sa signature. Pour utiliser un sous‑programme, il suffit de connaître sa signature; il n’est pas utile de connaître son implémentation. En ce sens, la signature est une abstraction du sous‑programme.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 // Déclaration du package package ch.epai.ict.m404.exercice1; // Définition de la classe App public class App { // Définition de la procédure principale public static void main(String[] args) { // Faire quelque chose d’utile System.out.println("Hello world !"); } }
Pour utiliser un sous‑programme, il suffit d’en connaître la ou les signatures (voir encadré). Par exemple, il n’est pas nécessaire de savoir comment est réalisée la procédure
println pour l’utiliser. Il suffit de savoir que ce sous‑programme est une procédure (il ne renvoie pas de valeur), qu’il s’appelle
println et qu’il a les signatures suivantes :
void println()
void println(boolean x)
void println(char x)
void println(char[] x)
void println(double x)
void println(float x)
void println(int x)
void println(long x)
void println(Object x)
void println(String x)
De même pour utiliser la fonction
Math.cos, il suffit de savoir que la fonction prend comme paramètre, une valeur de type double qui représente l’angle en radian, renvoie une valeur, de type double également, qui représente une approximation du cosinus de cet angle :
static double cos(double a)
Procédure
Une procédure est un sous‑programme qui ne renvoie pas de valeur et qui a un effet sur son environnement, c’est-à-dire qu’il modifie l’état du système dans lequel il est exécuté. Par exemple, l’effet peut être l’affectation d’une valeur à variable globale, l’affichage d’un texte à l’écran ou encore l’écriture de données dans un fichier. Un appel de procédure est une instruction.
La déclaration d’une procédure en Java se distingue de la déclaration d’une fonction par son type qui est toujours
void (vide) comme dans la définition de la procédure
main.
On voit assez fréquemment des exemples de code dans lesquels un sous‑programme qui devrait être une procédure utilise la valeur de retour pour signaler une erreur. Cette pratique n’est acceptable que dans le cas où le langage utilisé n’a pas de support pour les exceptions (p. ex. le langage C). Mais les exceptions sont désormais supportées par tous les langages courants ; Java, C#, C++, PHP et JavaScript supportent tous les exceptions.
Bien que cela soit rarement utile, il est possible et permis pour une procédure de n’avoir aucun paramètre ; l’effet peut dépendre de l’état du système. Pour terminer l’exécution d’une procédure et revenir au sous‑programme appelant, on peut utiliser l’instruction
return.
Fonction
Une fonction est, dans l’idéal, un sous‑programme qui renvoie une valeur qui ne dépend que des valeurs de ses paramètres. Si ce critère est rempli, on parle d’une fonction pure, dans le cas contraire, on parle d’une fonction avec effet de bord (voir plus bas). Un appel de fonction est une expression dont le type est celui de la fonction.
En Java, pour renvoyer une valeur au sous‑programme appelant, on utilise l’instruction
return avec une expression. L’expression renvoyée doit être du même type que la fonction. Le code de la figure 10 montre une implémentation possible d’une fonction de type
int qui renvoie la plus petite de deux valeurs et son utilisation dans diverses expressions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 // Déclaration du package package ch.epai.ict.m404.exercices; // Définition de la classe App public class App { // Définition de la procédure principale public static void main(String[] args) { int a = 10; int b = 25; // Invoque la fonction dans une expression int res = 4 * minOfTwo(a * 2, b) + 4; // Invoque la fonction dans une deuxième expression if (minOfTwo(b, a) < 20) { // Invoque encore la fonction dans une troisième expression System.out.printf("Le résultat est : %d%n", minOfTwo(res / 2, 43)); } else { System.out.printf("Le résultat est : %d%n", res); } } // Définition de la fonction minOfTwo public static int minOfTwo(int val1, int val2) { // Initialize min avec val1 par hypothèse int min = val1; // Si val2 est plus petit que min, change // la valeur de min. if (val2 < min) { min = val2; } // Renvoie la valeur de min return min; } }
Une fonction doit obligatoirement avoir une instruction
return pour chaque chemin d’exécution possible. Dans l’exemple précédent, cette condition est bien remplie puisque le
return est la dernière instruction. Le code de la figure 11 montre une autre implémentation possible de la même fonction. Dans cette version, on commence par tester si val1 est plus petit que val2 et si c’est le cas, on renvoie immédiatement val1. Dans un tel cas, il faut s’assurer que l’autre chemin d’exécution passe également par une instruction return. Si ce n’est pas le cas, Java refuse de compiler le programme.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // Déclaration du package package ch.epai.ict.m404.exercices; // Définition de la classe App public class App { // Définition de la procédure principale public static void main(String[] args) { int a = 21; int b = 11; int res = minOfTwo(a, b * 2) * 2; System.out.printf("Le résultat est : %d%n", res); } // Définition de la fonction minOfTwo public static int minOfTwo(int val1, int val2) { if (val1 < val2) { return val1; } else { return val2; } } }
Enfin, comme dans le cas des procédures, la valeur de retour d’une fonction ne devrait pas être utilisée pour signaler une erreur. Pour cela, il convient d’utiliser les exceptions.
Fonction avec effet de bord
Les fonctions pures ont beaucoup d’avantages – elles sont notamment plus faciles à tester –, mais avec un langage de programmation impératif, les fonctions avec effet de bord sont inévitables. Une fonction avec effet de bord est une fonction dont la valeur renvoyée dépend de l’état du système ou qui modifie l’état du système. Deux appels successifs d’une fonction avec effet de bord ne renvoient pas nécessairement la même valeur.
Par exemple, la fonction
nextInt de la classe
Scanner « consomme » un nombre entier dans flux d’entrée (
InputStream) et renvoie ce nombre à l’appelant. Après l’exécution de la fonction, l’entier « consommé » ne se trouve plus dans le flux. L’effet de bord est la modification du flux d’entrée. Un autre exemple de fonction avec effet de bord est la fonction
now de la classe
LocalDateTime qui renvoie la date et l’heure courante dont la valeur dépend de l’instant auquel elle est appelée.
Dans ces deux exemples, la valeur de la fonction ne dépend pas uniquement des valeurs de ses paramètres. Dans les deux cas, la fonction à un effet de bord.
Opération avec effet de bord
Certaines opérations – que l’on peut voir comme des cas particuliers de fonctions – sont des opérations avec effet de bord. C’est notamment le cas de l’opération d’instanciation (création) d’objets et de tableaux avec l’opérateur
new. Considérons, par exemple, le code suivant :
1 2 3 4 ... double[] array1 = new double[2]; double[] array2 = new double[2]; ...
L’expression de la partie droite (
new double[2]) est la même pour les deux affectations, mais la valeur de la variable
array1 n’est pas la même que la valeur de la variable de
array2. En effet, dans cette expression, l’opérateur
new crée un nouveau tableau et renvoie l’adresse (la position) à laquelle se trouve ce tableau dans la mémoire de l’ordinateur. La création du tableau est l’effet (de bord) de l’opération. Comme deux tableaux distincts ne peuvent pas occuper la même portion de mémoire, leurs adresses sont nécessairement différentes.
Utilisation d’expressions avec effet de bord
Du point de vue du langage, une expression avec effet de bord est une expression tout à fait ordinaire. Toutefois, pour éviter des erreurs particulièrement difficiles à diagnostiquer, on s’impose le principe suivant :
Si une expression a un effet de bord (appel d’une fonction avec effet de bord, création d’un objet ou d’un tableau, etc.), alors cette expression doit être la partie droite d’une affectation.
En particulier, l’utilisation d’une fonction avec effet de bord dans l’expression booléenne d’une structure de contrôle ou comme paramètre effectif dans l’appel d’un sous‑programme est une très mauvaise pratique qu’il est facile d’éviter. Le respect de cette règle justifie, en outre, l’abandon des opérations d’incrémentation et de décrémentation (
++ et
--). En effet, puisque ces opérations ont un effet de bord, une expression qui en fait usage doit, en vertu de ce principe, être la partie droite d’une affectation, ce qui n’a aucun sens. En Java, ces opérateurs peuvent être avantageusement remplacés par
+= 1 et
-= 1.