ProgrammationDéveloppeur Backend Go

Expliquez comment fonctionne la nomination et la portée des variables en Go lors des fonctions imbriquées et des boucles. Quels pièges faut-il prendre en compte ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

En Go, les règles de portée des variables (scoping) sont strictement définies par des blocs ({}), et le nom d'une variable peut être masqué (shadowing) dans les zones imbriquées. De nombreux pièges se présentent particulièrement dans les fonctions imbriquées, les fonctions anonymes, les boucles et lors de la déclaration de variables ayant le même nom à différents niveaux.

Historique du problème

Go a spécialement minimisé le comportement "magique" avec la portée, afin de rendre le code plus lisible. Cependant, la flexibilité de la syntaxe et la possibilité de redéclarer des variables par la forme courte := entraînent des erreurs de perception.

Problème

Si dans une fonction imbriquée ou un bloc de boucle une variable est déclarée avec le même nom que celui du niveau supérieur, la variable externe ne sera pas accessible (masquée – shadowed). Dans la plupart des cas, cela n'est pas remarqué par le compilateur et peut facilement causer des erreurs, en particulier lors du travail avec des closures. Une autre erreur fréquente est de déclarer une nouvelle variable dans un bloc if ou dans for-init, puis d'essayer d'y accéder en dehors du bloc.

Solution

Faites toujours attention aux niveaux de portée. N'utilisez pas les mêmes noms de variables dans les blocs imbriqués ou les fonctions anonymes sans réelle nécessité, évitez les noms courts et faites attention à l'utilisation de :=.

Exemple de code:

package main import "fmt" func main() { x := 1 { x := 2 // masque x de main() fmt.Println("Inner x:", x) } fmt.Println("Outer x:", x) for i := 0; i < 3; i++ { x := i // un nouveau x est créé à chaque itération go func() { fmt.Println("Goroutine x:", x) }() } }

Dans cet exemple, la variable externe x n'est pas modifiée, et un nouveau x est créé à l'intérieur du bloc. Dans la seconde boucle, la variable x est capturée dans la fonction imbriquée — le résultat peut être inattendu.

Caractéristiques clés :

  • Chaque portée (bloc) peut masquer des variables de niveau supérieur ;
  • Deux déclarations de variable avec le même nom ne sont pas liées, si elles se trouvent dans des portées différentes ;
  • Les closures capturent la variable, et non sa valeur au moment de l'itération ;
  • La forme courte := à l'intérieur d'un bloc crée toujours une nouvelle variable, même si l'externe existe déjà.

Questions piégées.

1. Quelle valeur de la variable sera imprimée en dernier dans le bloc imbriqué lors du masquage ?

La valeur de la variable externe, car la variable interne n'existe que dans le bloc.

2. Que se passe-t-il si l'on essaie d'accéder à une variable déclarée à l'intérieur d'un bloc if/for en dehors de ce bloc ?

Le compilateur renverra une erreur : variable en dehors de la portée.

if true { y := 5 } fmt.Println(y) // erreur

3. Comment éviter une valeur inattendue lors de la création d'une goroutine dans une boucle par rapport à une variable ?

En passant la variable comme paramètre de la fonction :

for i := 0; i < 3; i++ { go func(val int) { fmt.Println(val) }(i) }

Erreurs courantes et anti-patterns

  • Utiliser le même identifiant à différents niveaux — des données sont perdues, difficile à suivre ;
  • Capturer des variables de boucle dans une goroutine sans les passer explicitement comme argument ;
  • S’attendre à ce que la forme courte := modifie une variable déjà existante (elle créera une nouvelle variable).

Exemple de la vie réelle

Cas négatif

La boucle initialise plusieurs goroutines pour un traitement parallèle, mais à l'intérieur de la closure, la variable de boucle est utilisée sans transmission — toutes les goroutines travaillent avec sa "dernière" valeur.

Avantages :

  • Concis, peu de code.

Inconvénients :

  • Comportement imprévisible, bugs en production, données perdues.

Cas positif

Passer la variable de boucle comme paramètre de la closure — chaque goroutine reçoit sa propre valeur.

Avantages :

  • Fonctionnement correct, pas de concurrence de données ni de surprises.

Inconvénients :

  • Nécessite de spécifier explicitement la liste des paramètres de la fonction.