ProgrammationDéveloppeur Backend

Quelles sont les spécificités et les pièges de l'utilisation de defer avec des méthodes et des fonctions prenant des pointeurs et des valeurs en Go ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

En Go, le mot-clé defer retarde l'exécution d'une fonction jusqu'à la fin de la fonction englobante. C'est pratique pour libérer des ressources et finaliser. Cependant, l'utilisation de defer en combinaison avec des méthodes prenant des pointeurs (*T) ou des valeurs (T) présente des nuances subtiles.

Historique de la question

Pour des raisons de sécurité du code, Go a été conçu à l'origine pour garantir l'appel automatique du code de nettoyage, quel que soit le lieu de sortie de la fonction. Cependant, le type de receveur utilisé (pointeur ou valeur) détermine si l'objet de la méthode qui appelle defer est copié ou si l'objet original est modifié.

Problème

Lorsque nous appelons une méthode avec un receveur de valeur (T), la valeur de la structure est copiée dans defer. Si nous appelons une méthode avec un receveur pointeur (*T), la méthode travaille avec l'objet original. Par conséquent, les modifications des données dans la méthode defer par valeur ne seront pas visibles, tandis qu'à l'aide d'un pointeur, elles se refléteront sur la structure externe. Cela entraîne des erreurs difficiles à détecter, surtout lorsque l'on essaie de modifier l'état d'un objet via defer.

Solution

Lors de la conception de méthodes et de l'utilisation de defer, il convient de toujours choisir consciemment le type de receveur. Les modifications qui doivent subsister jusqu'à la fin de la fonction doivent être effectuées via un pointeur.

Exemple de code :

package main import "fmt" type Counter struct { Value int } func (c Counter) IncValue() { // méthode par valeur defer func() { c.Value++ // seulement la copie augmentera fmt.Println("[Valeur du receveur defer] Valeur :", c.Value) }() } func (c *Counter) IncPointer() { // méthode par pointeur defer func() { c.Value++ // l'original augmentera fmt.Println("[Pointeur du receveur defer] Valeur :", c.Value) }() } func main() { c := Counter{Value: 10} c.IncValue() // La valeur restera 10 c.IncPointer() // La valeur deviendra 11 fmt.Println("Valeur originale après les appels :", c.Value) }

Points clés :

  • Defer reçoit toujours une copie des arguments et des receveurs au moment de la déclaration.
  • Les méthodes avec un receveur de valeur n'affectent pas l'objet externe dans defer.
  • Les méthodes avec un receveur pointeur modifient l'original via defer.

Questions piégeuses.

1. L'objet externe changera-t-il si nous appelons dans defer une méthode prenant un receveur de valeur ?

Non, dans defer, l'objet original ne changera pas, car la méthode travaille avec une copie de la structure.

2. Peut-on compter sur defer pour garantir un changement d'état de la structure via une méthode de valeur ?

Non. Il faut utiliser des méthodes avec un pointeur si vous voulez refléter les changements sur l'objet original.

3. Que se passera-t-il si nous modifions les champs de la structure après la déclaration de defer ?

Les actions dépendent de la manière dont le receveur est passé : si la méthode/fonction reçoit une copie, les modifications après la déclaration de defer ne seront pas visibles dans la fonction différée.

func (c Counter) Demo() { defer fmt.Println("defer c.Value :", c.Value) // capturera la valeur actuelle c.Value = 42 // n'affectera pas defer }

Erreurs typiques et anti-patrons

  • Essayer de modifier l'objet d'origine par une méthode avec un receveur de valeur à l'intérieur de defer.
  • Ne pas faire attention à la copie des structures lors de l'utilisation de defer.

Exemple de la vie réelle

Cas négatif

Nous avions écrit une fonction pour fermer une connexion, mettant à jour l'état via defer avec une méthode de valeur. Il s'est avéré que le drapeau de fermeture ne se mettait pas à jour.

Avantages :

  • Code concis, lisible.

Inconvénients :

  • Le nettoyage ne se produisait pas - les connexions fuyaient.

Cas positif

Nous avons utilisé des méthodes avec un receveur pointeur pour la finalisation, modifiant l'objet original.

Avantages :

  • L'état change correctement, les ressources sont nettoyées.

Inconvénients :

  • Un contrôle strict est nécessaire sur les cas où utiliser un pointeur et où utiliser une valeur.