web-dev-qa-db-fra.com

Attraper la panique à Golang

Avec le code suivant, si aucun argument de fichier n’est donné, une panique s’ensuit pour la ligne 9 panic: runtime error: index out of range comme prévu.

Comment puis-je "attraper" cette panique et la gérer directement lorsque je lui passe quelque chose (os.Args[1]) qui provoque la panique? Tout comme try/catch in PHP ou try/excepté en Python.

J'ai effectué une recherche ici sur StackOverflow mais je n'ai rien trouvé qui réponde à cette question.

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open(os.Args[1])
    if err != nil {
        fmt.Println("Could not open file")
    }
    fmt.Printf("%s", file)
}
43
Luke B

Go is not python, vous devez vérifier correctement les arguments avant de l’utiliser:

func main() {
    if len(os.Args) != 2 {
         fmt.Printf("usage: %s [filename]\n", os.Args[0])
         os.Exit(1)
    }
    file, err := os.Open(os.Args[1])
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s", file)
}
41
OneOfOne

Un programme paniquant peut récupérer avec la fonction intégrée recover():

La fonction recover permet à un programme de gérer le comportement d’un goroutine paniqué. Supposons qu'une fonction G diffère une fonction D qui appelle recover et qu'une panique se produise dans une fonction du même goroutine dans lequel G est en cours d'exécution. Lorsque l'exécution des fonctions différées atteint D, la valeur de retour de l'appel de D à recover sera la valeur transmise à l'appel de panic. Si D retourne normalement, sans démarrer un nouveau panic, la séquence de panique s'arrête. Dans ce cas, l'état des fonctions appelées entre G et l'appel à panic est ignoré et l'exécution normale reprend. Toutes les fonctions différées par G avant D sont alors exécutées et l'exécution de G s'achève en retournant à l'appelant.

La valeur de retour de recover est nulle si l'une des conditions suivantes est remplie:

  • L'argument de panic était nil;
  • le goroutine ne panique pas;
  • recover n'a pas été appelé directement par une fonction différée.

Voici un exemple d'utilisation:

// access buf[i] and return an error if that fails.
func PanicExample(buf []int, i int) (x int, err error) {
    defer func() {
        // recover from panic if one occured. Set err to nil otherwise.
        if (recover() != nil) {
            err = errors.New("array index out of bounds")
        }
    }()

    x = buf[i]
}

Notez que plus souvent qu'autrement, paniquer n'est pas la bonne solution. Le paradigme Go consiste à vérifier les erreurs explicitement. Un programme ne doit paniquer que si les circonstances dans lesquelles il panique ne se produisent pas lors de l'exécution d'un programme ordinaire. Par exemple, ne pas pouvoir ouvrir un fichier est une chose qui peut arriver et qui ne devrait pas causer de panique alors que le manque de mémoire vaut la panique. Néanmoins, ce mécanisme existe pour pouvoir attraper même ces cas et peut-être fermer normalement.

87
fuz

Premièrement: vous ne voudriez pas faire ça. La gestion des erreurs try-catch-style n'est pas une gestion des erreurs. Dans Go, vous devriez vérifier len(os.Args) en premier et accéder à l'élément 1 uniquement s'il est présent.

Dans les rares cas où vous devez prendre des paniques (et votre cas est pas l'un d'entre eux!), Utilisez defer en combinaison avec recover. Voir http://golang.org/doc/effective_go.html#recover

14
Volker

Certains paquets officiels Golang utilisent panique/différé + récupère comme lancer/attraper, mais uniquement lorsqu'ils ont besoin de décompresser une pile d'appels volumineuse. Dans Le paquet json de Golang avec panique/différé + récupérer comme lancer/attraper est la solution la plus élégante.

de http://blog.golang.org/defer-panic-and-recover

Pour un exemple concret de panique et de récupération, consultez le package json de la bibliothèque standard Go. Il décode les données codées JSON avec un ensemble de fonctions récursives. Lorsque JSON mal formé est rencontré, l'analyseur appelle panic pour dérouler la pile jusqu'à l'appel de la fonction de niveau supérieur, qui récupère de la panique et renvoie une valeur d'erreur appropriée (voir les méthodes "error" et "unmarshal" du type decodeState dans decode.go).

Rechercher d.error( at http://golang.org/src/encoding/json/decode.go

Dans votre exemple, la solution "idiomatique" consiste à vérifier les paramètres avant de les utiliser, comme l'ont indiqué d'autres solutions.

Mais, si vous voulez/devez attraper n'importe quoi vous pouvez faire:

package main

import (
    "fmt"
    "os"
)

func main() {

    defer func() { //catch or finally
        if err := recover(); err != nil { //catch
            fmt.Fprintf(os.Stderr, "Exception: %v\n", err)
            os.Exit(1)
        }
    }()

    file, err := os.Open(os.Args[1])
    if err != nil {
        fmt.Println("Could not open file")
    }

    fmt.Printf("%s", file)
}
13
Lucio M. Tato

Nous pouvons gérer la panique sans arrêter le processus en utilisant la récupération. En appelant recover dans n'importe quelle fonction utilisant différer, l'exécution retournera à la fonction appelante. Récupérer renvoie deux valeurs: booléenne et interface de récupération. En utilisant l'assertion de type, nous pouvons obtenir une valeur d'erreur sous-jacente. Vous pouvez également imprimer une erreur sous-jacente à l'aide de recover.

defer func() {
    if r := recover(); r != nil {
        var ok bool
        err, ok = r.(error)
        if !ok {
            err = fmt.Errorf("pkg: %v", r)
        }
    }
}()
3
Himanshu

J'ai dû prendre des paniques dans un cas de test. J'ai été redirigé ici.

func.go

var errUnexpectedClose = errors.New("Unexpected Close")
func closeTransaction(a bool) {
    if a == true {
        panic(errUnexpectedClose)
    }
}

func_test.go

func TestExpectedPanic() {
    got := panicValue(func() { closeTransaction(true) })
    a, ok := got.(error)
    if a != errUnexpectedClose || !ok {
        t.Error("Expected ", errUnexpectedClose.Error())
    }
}

func panicValue(fn func()) (recovered interface{}) {
    defer func() {
        recovered = recover()
    }()
    fn()
    return
}

Utilisé à partir de https://github.com/golang/go/commit/e4f1d9cf2e948eb0f0bb91d7c253ab61dfff3a59 (ref de VonC)

2
Sairam

Notez que le traitement de récupération d'une panique Exécution erreur (telle que la tentative d'indexer un tableau en dehors des limites du déclencheur) peut changer avec go 1.7 après numéro 14965

Voir CL 21214 et son test :

runtime: les valeurs de panique d'erreur d'exécution implémentent l'interface Error

Faire en sorte que les paniques d'exécution implémentent Erreur comme demandé par Paniques d'exécution (specs) , au lieu de paniques avec des chaînes.

Lorsque vous récupérez une erreur de panique, vous pouvez:

if _, ok := recovered.(runtime.Error); !ok {

Ceci est encore en cours d'évaluation, et Dave Cheney . mentionne:

Je ne sais pas ce que les gens font actuellement, mais mon point de vue a été interrompu depuis longtemps et personne ne s'est plaint; ils se fient donc explicitement au comportement brisé ou personne ne s'en soucie. De toute façon, je pense que c'est une bonne idée d'éviter de faire ce changement.

1
VonC