ICS3U

Accueil > Programmer avec Java > Décomposition et modularité >

📚 Portée des variables

Survol et attentes

Définitions
Portée d’une variable
l’étendue du code source où le nom de variable est reconnue, se limitant aux accolades {} du bloc qui le contiennent. Intentionnellement, la portée des variables est limitée pour mieux contrôler l’accès aux informations et pour permettre la réutilisation des mêmes noms de variables dans différentes parties du code source sans créer des conflits de noms.
Variable globale
une variable déclarée en dehors de toutes les méthodes. Toutes les méthodes dans le fichier peuvent accéder directement à ces variables.
Variable locale
une variable déclarée dans une méthode ou, à l’intérieur des méthodes, dans une structure de contrôle (incluant leurs déclarations). C’est pourquoi les méthodes doivent recevoir des arguments et retourner des valeurs pour passer de l’information aux autres méthodes.
Constante globale
une variable globale qui est déclarée final pour indiquer qu’elle ne peut pas être modifiée. Les constantes globales sont utilisées pour des valeurs qui ne changent pas dans le programme et qui sont utilisées dans plusieurs méthodes. Par exemples : une référence au Scanner de la console, des valeurs de référence, des codes de couleur ANSI, des constantes mathématiques. Nommant ce type de valeur dans une constante globale rend le code plus lisible que d’inclure la valeur littérale partout où elle est utilisée.

Objectifs d’apprentissage

À la fin de cette leçon vous devrez être en mesure de :

Critères de succès

Portée des variables

La portée d’une variable est synonyme de sa visibilité : jusqu’à quels endroits dans le code est-ce qu’on peut “voir” cette variable (la nommer sans erreur de syntaxe)?

Les variables globales sont visibles partout dans un programme1. Elles sont déclarées à l’extérieur des méthodes et on peut les lire et les modifier dans toutes les méthodes du programme.

C’est un avantage parce qu’on a pas besoin d’échanger cette information entre les méthodes via des paramètres ou des valeurs de retour.

C’est un désavantage parce n’importe quelle partie de notre code peut modifier cette information, ce qui peut rendre le code difficile à corriger, briser la modularité du code (les sous-problèmes sont maintenant fusionnés autour de l’information partagée) ou introduire des problèmes de sécurité (modifications possibles au-delà des intentions originales).

Les variables locales sont seulement visibles dans la méthode où elles sont définies. Les paramètres et les variables définies dans le corps d’une méthode sont des variables locales. Jusqu’à présent, nous avons seulement utilisé des variables locales dans nos programmes.

Les avantages des variables locales sont que : l’information existe seulement à l’endroit où elle sera directement utile et plusieurs variables dans un programme peuvent avoir le même nom sans interférence parce que leurs portées ne se croisent pas.

Le désavantage est que pour partager l’information locale à une autre méthode, il faut “passer des messages” avec des arguments/paramètres et des valeurs de retour. Cela peut parfois rendre la liste des paramètres assez longue que c’est difficile à se rappeler quel paramètre va à quelle position.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
---------------fichier------------------

déclaration de variable globale


méthode() {

    déclaration de variable locale

}


méthode( les paramètres sont des variable locales ) {

}

----------------------------------------

Variables globales

C’est rarement une bonne idée d’utiliser des variables globales parce que le code devient beaucoup plus dur à déboguer. Mais il y a quelques cas où c’est une solution bien adaptée :

La valeur est constante et requise par plusieurs méthodes

On peut alors déclarer une constante globale en le préfixant avec le mot-clé final et en appliquant la convention du nom en majuscules. Les Scanner pour la console, les codes de couleur ANSI et les structures de données (à venir dans une prochaine section du cours) sont des exemples de bons candidats de constantes globales.

Dans ce cas, le fait que la valeur soit constante élimine la possibilité de modification inappropriée et la portée globale peut réduire la complexité et la répétitivité des déclarations et appels de méthodes.

La variable représente un état du programme que plusieurs méthodes doivent consulter ou peuvent modifier

Plusieurs informations dans un jeu tombent dans cette catégorie. Si ces variables n’étaient pas globales, la liste des paramètres pour la plupart des méthodes deviendrait ingérable. De plus, la restriction avec Java d’une seule valeur de retour rend l’exportation de plus qu’une modification à ces valeurs, si pas impossible, du moins très complexe. Cette complexité additionnelle rendrait le code plus dur à lire, à déboguer et à modifier. Les risques liés aux variables globales sont alors moindre que les risques avec l’autre approche.

Exemples de constantes globales

Fichier GlobalScanner.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.*;

final Scanner CONSOLE = new Scanner(System.in); // visible globalement

void main() {
    // final Scanner CONSOLE = new Scanner(System.in); // ne serait pas visible dans getName ni getInteger
    String name = getName();
    int age = getInteger("age");

    System.out.printf("%s a %d ans.\n", name, age);
}

String getName() {
    System.out.print("Quel est votre nom? > ");
    return CONSOLE.nextLine();
}

int getInteger(String what) {
    System.out.printf("Entrez un nombre entier pour votre %s > ", what);
    int result = CONSOLE.nextInt(); // pour consommer la valeur
    CONSOLE.nextLine(); // pour consommer le retour de ligne aussi
    return result;
}

Ici le Scanner pour la console est utilisée dans plusieurs méthodes et ne change pas de valeur (réfère toujours à System.in), alors c’est un bon candidat de constant globale.

Notez que la déclaration commence avec final disant à Java que cette valeur ne peut jamais changer durant l’exécution. Notez aussi qu’on a écrit le nom en majuscules afin de mieux le repérer comme constante.

Fichier GlobalColorCodes.java

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
import java.util.*;

final String RED = "\033[0;31m";
final String GREEN = "\033[0;32m";
final String YELLOW = "\033[0;33m";
final String RESET = "\033[0m";

void printRed(String text) {
   System.out.println(RED + text + RESET);
}

void printGreen(String text) {
   System.out.println(GREEN + text + RESET);
}

void printYellow(String text) {
   System.out.println(YELLOW + text + RESET);
}

void main() {
    printRed("Ceci est un message d'erreur!");
    printGreen("Ceci est un message de succès!");
    printYellow("Ceci est un avertissement");

    System.out.printf("%sCe texte%s est en %ssurbrillance%s%s!!!%s", 
            GREEN, RESET, YELLOW, RESET, RED, RESET);
}

Ici on a plusieurs constantes globales pour les différents codes de couleur pour le texte à la console. Ces constantes sont utilisées dans les différentes méthodes, incluant main qui fait un usage sur mesure pour la dernière phrase.

Variables globales d’état

Voici un exemple banal pour quelques variables globales pour un jeu.

Fichier : GlobalGameState.java

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
int lives = 3;
int xp = 0;
boolean hasWeapon = true;
boolean hasHorse = true;

void diedInBattle() {
    xp += 5;
    lives--;
    hasWeapon = false;
    System.out.println("Vous êtes mort au combat et avez perdu votre arme!");
}

void diedWhileRiding() {
    xp += 2;
    lives--;
    hasHorse = false;
    System.out.println("Vous êtes mort en tombant de votre cheval!");
}

void showStats() {
    System.out.println("Vies : " + lives);
    System.out.println("Points d'expérience : " + xp);
    System.out.println("Vous avez une arme : " + hasWeapon);
    System.out.println("Vous avez un cheval : " + hasHorse);
}

void main() {
    showStats();
    diedInBattle();
    showStats();
    diedWhileRiding();
    showStats();
}

Priorité de nom selon la portée

Une des caractéristiques d’un programme avec différentes portées de variables est que le même nom peut être déclaré à plusieurs endroits sans conflits en autant que leurs portées soient différentes.

S’il y a deux variables du même type et du même nom, mais une est globale est l’autre est locale, la version locale sera toujours utilisée en priorité.

Voici quelques exemples courts pour illustrer la chose.

1
2
3
4
5
6
7
8
9
/* Globale versus locale */

String name = "Steve";

void main() {
    String name = "Angelica";
    name = "Naomi";
    System.out.println(name);
}
Quelle variable est modifiée à la ligne 7? locale ou globale? La variable locale, déclarée à la ligne 6, car elle a priorité sur la variable globale.
Quel nom s'affiche? Naomi ou Steve?

Naomi. La variable locale dans main masque la variable globale (qui n’est plus accessible dans main à cause de ce masquage).

1
2
3
4
5
6
7
8
9
10
11
12
/* Locale versus locale */

void main() {
    String name = "Angelica";
    System.out.println(rename(name));
    System.out.println(name);
}

String rename(String name) {
    name = "Demonica";
    return name;
}
Qu'est-ce qui s'affiche à la ligne 5?

Demonica. C’est la valeur de retour de rename.

Qu'est-ce qui s'affiche à la ligne 6?

Angelica. Le paramètre name est locale à la méthode rename. Cette variable récoit la valeur de la variable dans main comme argument. Par contre, ce paramètre (locale à rename) n’existe plus après le retour de la méthode. rename retourne la nouvelle valeur, mais la variable name dans main ne la reçoit pas. La valeur de retour passe plutôt directement dans un print. Alors la variable locale dans main n’a subit aucune modification du début jusqu’à la fin.

Quelques exemples d’erreur (de conflit) de portée

Variable inaccessible

1
2
3
4
5
6
7
void isolated() {
    int answer = 7;
}

void main() {
    System.out.println(answer); // erreur "cannot find symbol"
}

Ici, answer est locale à la méthode isolated, alors il n’est pas visible dans main, contrairement à si cette variable avait été déclarée globalement, à l’extérieure d’une méthode.

1
2
3
4
5
int answer = 7; // déclaration globale

void main() {
    System.out.println(answer); // answer est visible
}

Déclaration dupliquée

1
2
3
4
5
6
7
8
String thing(String t) {
    String t = t + "2"; // erreur "duplicate local variable"
    return t;
}

void main() {
    System.out.println(thing("thing"));
}

Ici, dans la méthode thing on déclare déjà un paramètre String t. Alors quand on déclare une autre variable locale du même nom à la ligne 2, Java nous indique une erreur. Si l’intention était de simplement modifier la valeur reçue dans le paramètre alors on peut réécrire la ligne 2 comme ceci :

1
2
3
4
5
6
7
8
String thing(String t) {
    t = t + "2"; // référence valide au paramètre t déjà déclarée
    return t;
}

void main() {
    System.out.println(thing("thing"));
}

La différence est que la ligne 2 n’est plus une déclaration d’une nouvelle variable. Elle est désormais une référence à la variable déjà déclarée dans la liste de paramètres.

Exercices

📚 Tester la compréhension

Faire les deux questions de choix multiples au début de la page ici pour vérifier votre compréhension de la portée des variables. Ne faites pas les problèmes de programmation car ils intègrent d’autres concepts que nous ne verrons pas dans ce cours.

La raison que les exercices de programmation sur la page sont déconseillés est qu’ils sollicitent des concepts de la programmation orientée objet que nous n’avons pas appris. Ce sont plutôt des concepts du cours ICS4U.

Je vous suggère plutôt les exercices pratiques suivants qui sont mieux adaptés à notre contexte.

🛠️ Pratique

Travaillez dans le répertoire GitHub partagé par votre enseignant pour la pratique et les exercices.

Cette pratique vise à démontrer les différences entre une approche de variables globales et une approche de variables locales dans un cas où les deux sont des choix valides.

Le contexte est l’utilisation d’un symbole spécifique comme indicateur d’invite de réponses chaque fois qu’on pose une question à l’utilisateur. On veut que ce symbole soit facile à changer si on veut utiliser un autre symbole. On veut aussi que le symbole soit utilisé dans plusieurs méthodes.

Approche avec variable globale

Avec cette approche, on déclare une variable globale pour le symbole. Par la suite nous concatenons ce symbole à la fin de chaque question posée à l’utilisateur. Pour montrer l’utilité à travers plusieurs méthodes, chaque question se trouve dans sa propre méthode.

Le Scanner de la console est également déclaré comme variable globale pour un accès direct pour toutes les méthodes.

diagramme de méthodes pour var globale

Fichier GlobalVar.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.*;

final Scanner CONSOLE = new Scanner(System.in);
final String PROMPT = " > ";

void main() {
    String name = getName();
    int age = getAge();
    System.out.println("Votre nom est " + name + " et vous avez " + age + " ans.");
}

String getName(){
    System.out.print("Quel est votre prénom?" + PROMPT);
    String name = CONSOLE.next();
    CONSOLE.nextLine(); //vider le reste de la ligne
    return name;
}

int getAge() {
    System.out.print("Quel est votre âge?" + PROMPT);
    int age = CONSOLE.nextInt();
    CONSOLE.nextLine();
    return age;
}
  1. Copiez ce code dans un fichier GlobalVar.java et l’exécuter pour voir comment il fonctionne.
  2. Ajouter un commentaire après chaque déclaration de variable pour indiquer s’il s’agit d’une variable locale ou globale. Les variables sont CONSOLE, PROMPT; name, age; name; age.
  3. Modifiez la valeur de PROMPT pour utiliser un autre symbole que >. Lancez le programme de nouveau pour voir le changement.
  4. Renommez les variables dans main et lancez le programme pour voir si ça change le fonctionnement. Selon vos observations, est-ce que la variable nommée name dans main est le même objet en mémoire que la variable nommée name dans getName?

Approche avec variables locales seulement

Avec cette approche, les variables à partager sont déclarées localement dans main et partagé via le mécanisme argument-paramètre. De plus, au lieu de créer une variable pour le symbole prompt, on crée une méthode. Parce que chaque méthode est visible par les autres dans le fichier, toutes les autres méthodes peuvent l’appeler.

diagramme de dépendances pour var locale

Fichier LocalVar.java

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
import java.util.*;

void main() {
    Scanner console = new Scanner(System.in);
    String name = getName(console);
    int age = getAge(console);
    System.out.println("Votre nom est " + name + " et vous avez " + age + " ans.");
}

void prompt(String question) {
    System.out.print(question + " > ");
}

String getName(Scanner in) {
    prompt("Quel est votre nom?");
    String name = in.next();
    in.nextLine(); // vider le reste de la ligne
    return name;
}

int getAge(Scanner in) {
    prompt("Quel est votre âge?");
    int age = in.nextInt();
    in.nextLine();
    return age;
}
  1. Copiez ce code dans un fichier LocalVar.java et l’exécuter pour voir comment il fonctionne. Confirmez qu’il fonctionne - du point de vue de l’utilisateur - de manière identique à GlobalVar.
  2. Modifiez le programme pour changer le symbole d’invite de commande dans prompt(). Est-ce que c’était plus facile ou difficile ou équivalent à ce qu’il fallait faire la dernière fois?
  3. Énumérer les éléments que vous avez préférez de la version GlobalVar et les éléments que vous avez préférez dans LocalVar. Énumérer aussi des éléments que vous n’avez pas aimé dans les deux versions. Ajoutez ces listes dans un commentaire d’en-tête dans un troisième fichier PreferredVar.java. Combiner vos éléments préférés de chaque version dans cette troisième version ou apporter des modifications pour créer votre propre approche. Résoudre les erreurs de fusion et lancer ce troisième programme pour vérifier qu’à la console l’expérience reste identique pour l’utilisateur malgré les changements.
  1. Il y a des nuances à “partout dans un programme”, mais ces nuances ne sont pas importants dans ce cours. Pour votre curiosité, voici quand même un bref survol. Nos programmes tiennent dans un seul fichier .java, alors partout dans le fichier serait plus apte. Les fichiers .java représentent implicitement une classe Java. Alors partout dans la classe serait aussi plus précis. Mais les variables Java peuvent être qualifiées de publiques ou privées. Les variables privées sont seulement “globales” dans la classe, tandis que les variables publiques sont “globales” pour tout un package de classes (à l’extérieur de la classe elle-même). Ce concept s’étend à la portée des classes (publiques ou privées) dans les packages et la portée des packages (exportées ou non) dans des modules.