2
votes

Comment gérer plusieurs erreurs en go?

Quelle est la manière la plus idiomatique de gérer plusieurs erreurs dans go?

Dois-je essayer de boucler l'erreur et de renvoyer les deux?

if err := foo(bar, obj); err != nil {
    // how to do I avoid losing this error?
    err := l.fixup(obj)
    if err != nil {
        //  but this error is the not the one from foo?
    }
    return err
}
return l.fixup(obj)


3 commentaires

Votre première ligne est un exemple de ce que vous pouvez faire, mettre dans une nouvelle portée de bloc. Y a-t-il une raison pour laquelle vous ne faites pas cela?


Il existe une dépendance sur foo. Si toto échoue, la correction doit voir cela. Fixup doit être appelé peu importe après foo mais a des comportements différents en fonction du résultat de foo. Vraiment, je veux voir l'erreur de foo car cela indique un problème, la correction ne devrait pas échouer


Alors pourquoi ne pas les nommer différemment? La solution évidente est généralement acceptable.


4 Réponses :


0
votes

La méthode fixup est appelée dans les deux chemins de code de la question. Simplifiez le code en appelant fixup en dehors de l'instruction if.

Si vous voulez que l'erreur de foo ait priorité sur l'erreur de fixup , alors faites exactement cela.

err1 := foo(bar, obj)
err2 := l.fixup(obj)
if err1 != nil {
   return err1
} 
return err2


0 commentaires

1
votes

Si vous devez enchaîner les erreurs et les renvoyer, tout dépend de la signification de votre erreur et de celle dont vous voulez informer l'appelant. Habituellement, lorsque l'occurrence d'une erreur ne doit pas arrêter le chemin et qu'un appel suit, tel que foo ici puis fixup , vous consigneriez la première erreur et renvoyez la seconde un, car il est probablement le plus pertinent pour ce que fait votre fonction.

Il existe également des packages pour encapsuler les erreurs, afin que vous puissiez créer une erreur à partir de plusieurs erreurs.

Il existe le package standard avec fmt.Errorf vous pouvez assembler plusieurs erreurs.

Il y a aussi https://github.com/hashicorp/go-multierror qui vous permet de conserver plusieurs erreurs dans une erreur.

Dans votre cas, si vous voulez faire apparaître les deux messages d'erreur, je ferais quelque chose comme ça:

err := foo(bar, obj)

if fixupErr := l.fixup(obj); fixupErr != nil {
    if err != nil {
        return fmt.Errorf("foo err: %s\nfixup err: %s\n", err, fixupErr)
    }
    return fixupErr
}
return err


0 commentaires

0
votes

Vous codez des appels l.fixup (obj) quoi qu'il arrive. Si foo (bar, obj) renvoie une erreur, une certaine manipulation est effectuée et l.fixup (obj) est appelé - sinon seulement l.fixup (obj) est appelé. Par conséquent, votre code peut être réorganisé comme ceci:

package main

import (
    "errors"
    "fmt"
)

// FooError is the error to be returned by foo
type FooError struct {
    Bar string
}

// Error implements the interface
func (f FooError) Error() string {
    return fmt.Sprintf("%s: interface is nil", f.Bar)
}

// dummy foo func
func foo(bar string, in interface{}) error {
    if in == nil {
        return FooError{Bar: bar}
    }
    return nil
}

// dummy fixup func
func fixup(in interface{}) error {
    if in == nil {
        return errors.New("Interface is nil")
    }
    return nil
}

// a wrapper, containing a variation of above code
func wrap(bar string) error {
    if err := foo(bar, nil); err != nil {
        // handle error from foo(bar,obj)
        // you can even return it, if you wanted to
        return err
    }
    return fixup(nil)
}

func main() {
    err := wrap("test")

    // The type switch itself
    switch err.(type) {
    case FooError:
        // We have a FooError, so we can deal with it accordingly
        fmt.Println("Foo Error:",err)
    default:
        // Every other error is handled by the default block
        fmt.Println("Std Error:",err)
    }
}

De plus, vous pouvez utiliser le fait que error est une interface à votre avantage si vous voulez faire la distinction entre les erreur potentiellement renvoyée par foo ou l.fixup . Vous pouvez le faire en créant une erreur typée pour l'un (ou les deux) et évaluer le type de l'erreur en utilisant ce qu'on appelle un commutateur de type.

// err will only be valid within the if-then-else-construct
if err := foo(bar, obj); err != nil {
    // handle error from foo(bar,obj)
    // you can even return it, if you wanted to
    // For the sake of this example, we simply log it
    log.Println("Executing foo: %s", err)
}
return l.fixup(obj)

Cependant, cela ne semble pas tout à fait correct. Si foo est appelé et qu'il renvoie une erreur empêche que quelque chose d'autre dans votre logique ne soit pas exécuté, il peut être judicieux de paniquer à la place.


0 commentaires

2
votes

Vous pouvez ajouter un contexte à votre erreur d'origine en utilisant la fonction Wrap de cet excellent package de Dave Cheney https://github.com/pkg/errors

errors.Wrap code> renvoie une nouvelle erreur qui ajoute du contexte à l'erreur d'origine.

if cause := foo(bar, obj); cause != nil {
    err := l.fixup(obj)
    if err != nil {
        return errors.Wrap(cause, err.Error())
    }
    return cause
}
return l.fixup(obj)

dans votre cas, ce serait:

func Wrap(cause error, message string) error


1 commentaires

Merci! C'est ce qui a décidé d'aller avec