1 Introduction à la fonction defer
en Golang
En langage Go, l'instruction defer
retarde l'exécution de l'appel de fonction suivant jusqu'à ce que la fonction contenant l'instruction defer
soit sur le point de terminer son exécution. On peut le comparer au bloc finally
dans d'autres langages de programmation, mais l'utilisation de defer
est plus flexible et unique.
L'avantage d'utiliser defer
est qu'il peut être utilisé pour effectuer des tâches de nettoyage, telles que la fermeture de fichiers, le déverrouillage de mutex ou simplement l'enregistrement de l'heure de sortie d'une fonction. Cela peut rendre le programme plus robuste et réduire la quantité de travail de programmation dans la gestion des exceptions. Dans la philosophie de conception de Go, l'utilisation de defer
est recommandée car elle contribue à garder le code concis et lisible lors de la gestion des erreurs, du nettoyage des ressources et d'autres opérations ultérieures.
2 Principe de fonctionnement de defer
2.1 Principe de base de fonctionnement
Le principe de base de defer
est d'utiliser une pile (principe du dernier entré, premier sorti) pour stocker chaque fonction différée à exécuter. Lorsqu'une instruction defer
apparaît, le langage Go n'exécute pas immédiatement la fonction suivant l'instruction. Au lieu de cela, il la place dans une pile dédiée. Ce n'est que lorsque la fonction externe est sur le point de retourner que ces fonctions différées seront exécutées dans l'ordre de la pile, la fonction dans la dernière instruction defer
déclarée étant exécutée en premier.
De plus, il convient de noter que les paramètres des fonctions suivant l'instruction defer
sont calculés et fixes au moment où le defer
est déclaré, et non lors de l'exécution effective.
func exemple() {
defer fmt.Println("monde") // différée
fmt.Println("bonjour")
}
func principal() {
exemple()
}
Le code ci-dessus produira en sortie :
bonjour
monde
"monde" est imprimé avant que la fonction exemple
se termine, même s'il apparaît avant "bonjour" dans le code.
2.2 Ordre d'exécution de plusieurs instructions defer
Lorsqu'une fonction comporte plusieurs instructions defer
, elles seront exécutées selon un principe de dernier entré, premier sorti. Cela est souvent très important pour comprendre la logique de nettoyage complexe. L'exemple suivant illustre l'ordre d'exécution de plusieurs instructions defer
:
func multiplesDelais() {
defer fmt.Println("Première différée")
defer fmt.Println("Deuxième différée")
defer fmt.Println("Troisième différée")
fmt.Println("Corps de la fonction")
}
func principal() {
multiplesDelais()
}
La sortie de ce code sera :
Corps de la fonction
Troisième différée
Deuxième différée
Première différée
Étant donné que defer
suit le principe du dernier entré, premier sorti, même si "Première différée" est la première différée, elle sera exécutée en dernier.
3 Applications de defer
dans différents scénarios
3.1 Libération des ressources
En langage Go, l'instruction defer
est couramment utilisée pour gérer la logique de libération des ressources, telles que les opérations de fichiers et les connexions de base de données. defer
garantit qu'après l'exécution de la fonction, les ressources correspondantes seront correctement libérées, quelle que soit la raison de la sortie de la fonction.
Exemple d'opération de fichier :
func LireFichier(nomFichier string) {
fichier, err := os.Open(nomFichier)
if err != nil {
log.Fatal(err)
}
// Utiliser defer pour s'assurer que le fichier est fermé
defer fichier.Close()
// Effectuer des opérations de lecture de fichier...
}
Dans cet exemple, une fois que os.Open
ouvre avec succès le fichier, l'instruction defer fichier.Close()
garantit que la ressource du fichier sera correctement fermée et que la poignée de fichier sera libérée à la fin de la fonction.
Exemple de connexion de base de données :
func InterrogerBaseDeDonnees(requete string) {
bd, err := sql.Open("mysql", "utilisateur:motDePasse@/nomBaseDeDonnées")
if err != nil {
log.Fatal(err)
}
// Assurer la fermeture de la connexion à la base de données en utilisant defer
defer bd.Close()
// Effectuer des opérations de requête de base de données...
}
De même, defer bd.Close()
garantit que la connexion à la base de données sera fermée à la sortie de la fonction InterrogerBaseDeDonnees
, quelle que soit la raison (retour normal ou exception levée).
3.2 Opérations de verrouillage en programmation concurrente
En programmation concurrente, l'utilisation de defer
pour gérer la libération de verrous mutex est une bonne pratique. Cela garantit que le verrou est correctement libéré après l'exécution du code de la section critique, évitant ainsi les interblocages.
Exemple de verrou mutex :
var mutex sync.Mutex
func updateSharedResource() {
mutex.Lock()
// Utilisez defer pour vous assurer que le verrou est libéré
defer mutex.Unlock()
// Effectuez des modifications sur la ressource partagée...
}
Peu importe si la modification de la ressource partagée réussit ou si une panique se produit entre-temps, defer
s'assurera que Unlock()
est appelé, permettant à d'autres goroutines en attente du verrou de l'acquérir.
Astuce : Des explications détaillées sur les verrous mutex seront couvertes dans les chapitres suivants. À ce stade, comprendre les scénarios d'application de defer est suffisant.
3 Pièges courants et considérations pour defer
Lors de l'utilisation de defer
, bien que la lisibilité et la maintenabilité du code soient grandement améliorées, il y a aussi des pièges et des considérations à garder à l'esprit.
3.1 Les paramètres des fonctions différées sont évalués immédiatement
func printValue(v int) {
fmt.Println("Valeur :", v)
}
func main() {
valeur := 1
defer printValue(valeur)
// La modification de la valeur de `valeur` n'affectera pas le paramètre déjà passé à defer
valeur = 2
}
// Le résultat sera "Valeur : 1"
Malgré le changement de la valeur de valeur
après l'instruction defer
, le paramètre passé à printValue
dans le defer
est déjà évalué et fixe, donc la sortie sera toujours "Valeur : 1".
3.2 Soyez prudent lors de l'utilisation de defer à l'intérieur des boucles
Utiliser defer
à l'intérieur d'une boucle peut entraîner des ressources qui ne sont pas libérées avant la fin de la boucle, ce qui peut entraîner des fuites ou l'épuisement des ressources.
3.3 Évitez le "libérer après utilisation" en programmation concurrente
Dans les programmes concurrents, lors de l'utilisation de defer
pour libérer des ressources, il est important de s'assurer que toutes les goroutines n'essaieront pas d'accéder à la ressource après sa libération, pour éviter les conditions de concurrence.
4. Notez l'ordre d'exécution des instructions defer
Les instructions defer
suivent le principe du dernier entré, premier sorti (LIFO), où le defer
déclaré en dernier sera exécuté en premier.
Solutions et meilleures pratiques :
- Soyez toujours conscient que les paramètres de fonction dans les instructions
defer
sont évalués au moment de la déclaration. - Lors de l'utilisation de
defer
à l'intérieur d'une boucle, envisagez d'utiliser des fonctions anonymes ou d'appeler explicitement la libération des ressources. - Dans un environnement concurrent, assurez-vous que toutes les goroutines ont terminé leurs opérations avant d'utiliser
defer
pour libérer des ressources. - Lors de l'écriture de fonctions contenant plusieurs instructions
defer
, considérez soigneusement leur ordre d'exécution et leur logique.
Suivre ces meilleures pratiques peut éviter la plupart des problèmes rencontrés lors de l'utilisation de defer
et conduire à l'écriture d'un code Go plus robuste et maintenable.