ProgrammationDéveloppeur Go

Comment fonctionne le `defer` en Go en interaction avec le return et les panics, et quels dangers peut-on rencontrer en modifiant les valeurs de retour à l'intérieur de `defer` ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historiquement, le concept de defer a été introduit dans Go pour libérer des ressources en toute sécurité et finaliser des actions indépendamment de la façon dont la fonction se termine (normalement ou en raison d'une panic). Cependant, l'interaction entre defer, return et panic présente plusieurs pièges fâcheux que même les développeurs expérimentés oublient souvent.

Problème : l'ordre de calcul des valeurs de retour, le fonctionnement des valeurs de retour nommées et la modification de ces valeurs dans defer diffèrent fortement du comportement habituel dans de nombreux langages. De plus, des erreurs peuvent survenir si une tentative de modification des valeurs déjà calculées est effectuée dans defer, provoquant un comportement inattendu.

Solution : il est toujours important de se rappeler que les valeurs que renvoie une fonction sont calculées AVANT l'exécution de defer, mais si des résultats nommés sont utilisés, ils peuvent être modifiés à l'intérieur de defer avant le retour effectif de la fonction.

Exemple de code :

func tricky() (res int) { defer func() { res = 42 // Change le valeur de retour ! }() return 10 } func main() { fmt.Println(tricky()) // Affichera 42 au lieu de 10 }

Caractéristiques clés :

  • defer s'exécute toujours après le calcul des arguments de return, mais avant le retour effectif de la fonction.
  • La modification des valeurs de retour nommées à l'intérieur de defer influence la valeur de retour.
  • Si une panic se produit, toutes les fonctions defferées s'exécutent avant le passage à recover ou la sortie du programme.

Questions pièges.

Dans quel ordre les fonctions defferées (defer) sont-elles exécutées ?

Elles s'exécutent strictement dans l'ordre inverse de leur déclaration (pile — LIFO).

func f() { defer fmt.Println("1") defer fmt.Println("2") } // Affichera : 2, puis 1

Quand les paramètres des fonctions defer sont-ils calculés — au moment de la déclaration de defer ou lors de son exécution ?

Les paramètres pour la fonction defer sont calculés au moment de la déclaration de defer, et non lors de l'appel.

func f() { i := 1 defer fmt.Println(i) // affichera 1, même si i change plus tard i = 2 }

Le defer peut-il modifier un résultat non nommé d'une fonction ?

Non. Seules les valeurs de retour nommées peuvent être modifiées dans defer.

func f() int { defer func() { /* ne rien changer */ }() return 5 }

Erreurs typiques et anti-patterns

  • Attendre de modifier un résultat anonyme (non nommé) via defer.
  • Modifier une valeur de retour via defer sans nécessité, ce qui conduit à un comportement imprévisible et à des bogues complexes.
  • Ne pas prendre en compte l'ordre de calcul et le passage des paramètres dans defer.

Exemple de la vie réelle

Cas négatif

Un jeune développeur voulait enregistrer le code de retour dans defer et a par erreur modifié une valeur de retour nommée, ce qui a "effacé" le résultat réel de la fonction.

Avantages :

  • Correction rapide de l'erreur logique.

Inconvénients :

  • Retour d'une valeur incorrecte, débogage difficile.

Cas positif

Dans une autre situation, defer a été utilisé uniquement pour libérer des ressources, enregistrer et n'a pas modifié le retour, tandis que les valeurs importantes ont été explicitement affectées avant le return.

Avantages :

  • Transparence, prévisibilité du comportement.

Inconvénients :

  • Doit ajouter explicitement des lignes de code supplémentaires si un effet secondaire est nécessaire au moment de la sortie.