Contrôle avec les fonctions Defer, Panic et Recover

Effectué

Examinons maintenant certains flux de contrôle qui sont propres à Go : defer , panic et recover. Chacune de ces fonctions a plusieurs cas d’usage. Vous y découvrirez les cas d’usage les plus importants.

Fonction de différenciation

Dans Go, une instruction defer diffère l’exécution d’une fonction (y compris les paramètres) jusqu’à ce que la fonction qui contient l’instruction defer se termine. En règle générale, vous pouvez différer une fonction lorsque vous souhaitez éviter d’oublier des tâches telles que la fermeture d’un fichier ou l’exécution d’un processus de nettoyage.

Vous pouvez reporter autant de fonctions que vous le souhaitez. Les instructions defer s’exécutent dans l’ordre inverse, de la dernière à la première.

Découvrez comment ce modèle fonctionne en exécutant l’exemple de code suivant :

package main

import "fmt"

func main() {
    for i := 1; i <= 4; i++ {
        defer fmt.Println("deferred", -i)
        fmt.Println("regular", i)
    }
}

Voici la sortie du code :

regular 1
regular 2
regular 3
regular 4
deferred -4
deferred -3
deferred -2
deferred -1

Dans cet exemple, notez que chaque fois que fmt.Println("deferred", -i) a été différée, la valeur de i a été stockée et l’appel de fonction a été ajouté à une file d’attente. Une fois que la fonction main() a fini d’imprimer les valeurs de regular, tous les appels différés ont été exécutés. Notez que la sortie des appels différés est dans l’ordre inverse (dernier entré, premier sorti), car ils sont retirés de la file d’attente.

Un cas d’usage typique de la fonction defer consiste à fermer un fichier une fois que vous avez fini de l’utiliser. Voici un exemple :

package main

import (
    "io"
    "os"
    "fmt"
)

func main() {
    newfile, error := os.Create("learnGo.txt")
    if error != nil {
        fmt.Println("Error: Could not create file.")
        return
    }
    defer newfile.Close()

    if _, error = io.WriteString(newfile, "Learning Go!"); error != nil {
	    fmt.Println("Error: Could not write to file.")
        return
    }

    newfile.Sync()
}

Une fois que vous avez créé ou ouvert un fichier, vous pouvez différer la fonction .Close() pour éviter d’oublier de fermer le fichier quand vous avez terminé.

Fonction d’alerte

Les erreurs d’exécution font que le programme Go envoie des alertes, par exemple lors de la tentative d’accès à un tableau à l’aide d’un index hors limites ou de la suppression d’une référence à un pointeur Null. Vous pouvez également forcer un programme à envoyer des alertes.

La fonction panic() intégrée arrête le flux normal du contrôle dans un programme Go. Quand vous utilisez un appel panic, tous les appels de fonction différés s’exécutent normalement. Le processus continue la pile jusqu’à ce que toutes les fonctions retournent. Le programme se bloque ensuite avec un message de journal. Le message contient une information sur l’erreur et une arborescence des appels de procédure pour vous aider à diagnostiquer la cause racine du problème.

Quand vous appelez la fonction panic(), vous pouvez ajouter n’importe quelle valeur en tant qu’argument. En règle générale, vous envoyez un message d’erreur indiquant la raison pour laquelle vous rencontrez un état d’alerte.

Par exemple, le code suivant combine les fonctions panic et defer. Essayez d’exécuter ce code pour voir comment le flux de contrôle est interrompu. Notez que les processus de nettoyage s’exécutent toujours.

package main

import "fmt"

func highlow(high int, low int) {
    if high < low {
        fmt.Println("Panic!")
        panic("highlow() low greater than high")
    }
    defer fmt.Println("Deferred: highlow(", high, ",", low, ")")
    fmt.Println("Call: highlow(", high, ",", low, ")")

    highlow(high, low + 1)
}

func main() {
    highlow(2, 0)
    fmt.Println("Program finished successfully!")
}

Voici le format :

Call: highlow( 2 , 0 )
Call: highlow( 2 , 1 )
Call: highlow( 2 , 2 )
Panic!
Deferred: highlow( 2 , 2 )
Deferred: highlow( 2 , 1 )
Deferred: highlow( 2 , 0 )
panic: highlow() low greater than high

goroutine 1 [running]:
main.highlow(0x2, 0x3)
	/tmp/sandbox/prog.go:13 +0x34c
main.highlow(0x2, 0x2)
	/tmp/sandbox/prog.go:18 +0x298
main.highlow(0x2, 0x1)
	/tmp/sandbox/prog.go:18 +0x298
main.highlow(0x2, 0x0)
	/tmp/sandbox/prog.go:18 +0x298
main.main()
	/tmp/sandbox/prog.go:6 +0x37

Program exited: status 2.

Voici ce qui se passe lorsque le code s’exécute :

  1. Tout s’exécute normalement. Le programme imprime les valeurs haute et basse passées dans la fonction highlow().

  2. Lorsque la valeur de low est supérieure à la valeur de high, le programme envoie une alerte. Le message Panic! s’affiche. À ce stade, le flux de contrôle est interrompu et toutes les fonctions différées commencent à imprimer le message Deferred....

  3. Le programme se bloque et vous voyez la trace de la pile complète. Le message Program finished successfully! s’affiche.

Un appel à la fonction panic() s’exécute généralement lorsque des erreurs graves ne sont pas attendues. Pour éviter un blocage de programme, vous pouvez utiliser une autre fonction nommée recover().

Fonction de récupération

Il peut arriver que vous souhaitiez éviter un blocage du programme et signaler l’erreur en interne. Ou peut-être souhaitez-vous nettoyer le désordre avant de laisser le programme se bloquer. Par exemple, vous souhaiterez peut-être fermer toute connexion à une ressource pour éviter d’autres problèmes.

Go fournit la fonction intégrée recover() pour vous permettre de reprendre le contrôle après une alerte. Vous appelez uniquement recover dans une fonction où vous appelez également defer. Si vous appelez la fonction recover(), elle retourne nil et n’a aucun autre effet en cours d’exécution normale.

Essayez de modifier la fonction main dans le code précédent pour ajouter un appel à la fonction recover(), comme suit :

func main() {
    defer func() {
	handler := recover()
        if handler != nil {
            fmt.Println("main(): recover", handler)
        }
    }()

    highlow(2, 0)
    fmt.Println("Program finished successfully!")
}

Lorsque vous exécutez le programme, la sortie doit ressembler à ceci :

Call: highlow( 2 , 0 )
Call: highlow( 2 , 1 )
Call: highlow( 2 , 2 )
Panic!
Deferred: highlow( 2 , 2 )
Deferred: highlow( 2 , 1 )
Deferred: highlow( 2 , 0 )
main(): recover from panic highlow() low greater than high

Program exited.

Voyez-vous la différence par rapport à la version précédente ? La principale différence réside dans le fait que vous ne voyez plus l’erreur de trace de la pile.

Dans la fonction main(), vous pouvez différer une fonction anonyme où vous appelez la fonction recover(). Un appel à recover() ne parvient pas à retourner nil lorsque le programme est en alerte. Vous pouvez effectuer une opération ici pour remettre de l’ordre, mais dans ce cas, vous imprimez simplement un composant.

La combinaison des fonctions panic et recover est la méthode idiomatique utilisée par Go pour gérer les exceptions. D’autres langages de programmation utilisent le mot clé try/catch différemment. Go préfère l’approche que vous avez explorée ici.

Pour plus d’informations, consultez la proposition pour ajouter une fonction try intégrée dans Go.