introduction
Go propose deux méthodes pour créer des erreurs dans la bibliothèque standard,errors.New
and fmt.Errorf
. Lorsque vous communiquez des informations d'erreur plus complexes à vos utilisateurs ou à votre futur utilisateur lors du débogage, ces deux mécanismes ne suffisent parfois pas pour capturer et signaler correctement ce qui s'est passé. Pour transmettre ces informations d'erreur plus complexes et obtenir plus de fonctionnalités, nous pouvons implémenter le type d'interface de bibliothèque standard,error
.
La syntaxe pour cela serait la suivante:
type error interface {
Error() string
}
Le packagebuiltin
définiterror
comme une interface avec une seule méthodeError()
qui renvoie un message d'erreur sous forme de chaîne. En implémentant cette méthode, nous pouvons transformer n'importe quel type défini en une erreur de notre part.
Essayons d'exécuter l'exemple suivant pour voir une implémentation de l'interfaceerror
:
package main
import (
"fmt"
"os"
)
type MyError struct{}
func (m *MyError) Error() string {
return "boom"
}
func sayHello() (string, error) {
return "", &MyError{}
}
func main() {
s, err := sayHello()
if err != nil {
fmt.Println("unexpected error: err:", err)
os.Exit(1)
}
fmt.Println("The string:", s)
}
Nous verrons le résultat suivant:
Outputunexpected error: err: boom
exit status 1
Ici, nous avons créé un nouveau type de structure vide,MyError
, et défini la méthodeError()
dessus. La méthodeError()
renvoie la chaîne"boom"
.
Dansmain()
, nous appelons la fonctionsayHello
qui renvoie une chaîne vide et une nouvelle instance deMyError
. PuisquesayHello
renverra toujours une erreur, l'appel defmt.Println
dans le corps de l'instruction if dansmain()
s'exécutera toujours. Nous utilisons ensuitefmt.Println
pour afficher la courte chaîne de préfixe"unexpected error:"
avec l'instance deMyError
contenue dans la variableerr
.
Notez que nous n'avons pas besoin d'appeler directementError()
, puisque le packagefmt
est capable de détecter automatiquement qu'il s'agit d'une implémentation deerror
. Il appelleError()
transparently pour obtenir la chaîne"boom"
et la concatène avec la chaîne de préfixe"unexpected error: err:"
.
Collecte d'informations détaillées dans une erreur personnalisée
Parfois, une erreur personnalisée est le moyen le plus propre de capturer des informations d'erreur détaillées. Par exemple, supposons que nous souhaitons capturer le code d’état des erreurs générées par une requête HTTP; exécutez le programme suivant pour voir une implémentation deerror
qui nous permet de capturer proprement ces informations:
package main
import (
"errors"
"fmt"
"os"
)
type RequestError struct {
StatusCode int
Err error
}
func (r *RequestError) Error() string {
return fmt.Sprintf("status %d: err %v", r.StatusCode, r.Err)
}
func doRequest() error {
return &RequestError{
StatusCode: 503,
Err: errors.New("unavailable"),
}
}
func main() {
err := doRequest()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println("success!")
}
Nous verrons la sortie suivante:
Outputstatus 503: err unavailable
exit status 1
Dans cet exemple, nous créons une nouvelle instance deRequestError
et fournissons le code d'état et une erreur en utilisant la fonctionerrors.New
de la bibliothèque standard. Nous imprimons ensuite ceci en utilisantfmt.Println
comme dans les exemples précédents.
Dans la méthodeError()
deRequestError
, nous utilisons la fonctionfmt.Sprintf
pour construire une chaîne en utilisant les informations fournies lors de la création de l'erreur.
Assertions de type et erreurs personnalisées
L'interface deerror
n'expose qu'une seule méthode, mais nous pouvons avoir besoin d'accéder aux autres méthodes des implémentations deerror
pour gérer correctement une erreur. Par exemple, nous pouvons avoir plusieurs implémentations personnalisées deerror
qui sont temporaires et peuvent être réessayées - dénotées par la présence d'une méthodeTemporary()
.
Les interfaces fournissent une vue étroite de l'ensemble plus large des méthodes fournies par les types, nous devons donc utiliser untype assertion pour changer les méthodes que la vue affiche, ou pour la supprimer complètement.
L'exemple suivant augmente lesRequestError
indiqués précédemment pour avoir une méthodeTemporary()
qui indiquera si les appelants doivent réessayer la demande:
package main
import (
"errors"
"fmt"
"net/http"
"os"
)
type RequestError struct {
StatusCode int
Err error
}
func (r *RequestError) Error() string {
return r.Err.Error()
}
func (r *RequestError) Temporary() bool {
return r.StatusCode == http.StatusServiceUnavailable // 503
}
func doRequest() error {
return &RequestError{
StatusCode: 503,
Err: errors.New("unavailable"),
}
}
func main() {
err := doRequest()
if err != nil {
fmt.Println(err)
re, ok := err.(*RequestError)
if ok {
if re.Temporary() {
fmt.Println("This request can be tried again")
} else {
fmt.Println("This request cannot be tried again")
}
}
os.Exit(1)
}
fmt.Println("success!")
}
Nous verrons la sortie suivante:
Outputunavailable
This request can be tried again
exit status 1
Dansmain()
, nous appelonsdoRequest()
qui nous renvoie une interfaceerror
. Nous imprimons d'abord le message d'erreur renvoyé par la méthodeError()
. Ensuite, nous essayons d'exposer toutes les méthodes deRequestError
en utilisant l'assertion de typere, ok := err.(*RequestError)
. Si l'assertion de type réussit, nous utilisons alors la méthodeTemporary()
pour voir si cette erreur est une erreur temporaire. Puisque leStatusCode
défini pardoRequest()
est503
, ce qui correspond àhttp.StatusServiceUnavailable
, cela renvoietrue
et entraîne l'impression de"This request can be tried again"
. En pratique, nous ferions plutôt une autre demande plutôt que d’imprimer un message.
Erreurs d'emballage
Généralement, une erreur est générée à partir de quelque chose en dehors de votre programme, tel que: une base de données, une connexion réseau, etc. Les messages d'erreur fournis à partir de ces erreurs n'aident personne à trouver l'origine de l'erreur. Des erreurs d’emballage avec des informations supplémentaires au début d’un message d’erreur fourniraient le contexte nécessaire au débogage.
L'exemple suivant montre comment nous pouvons attacher des informations contextuelles à unerror
autrement crypté renvoyé par une autre fonction:
package main
import (
"errors"
"fmt"
)
type WrappedError struct {
Context string
Err error
}
func (w *WrappedError) Error() string {
return fmt.Sprintf("%s: %v", w.Context, w.Err)
}
func Wrap(err error, info string) *WrappedError {
return &WrappedError{
Context: info,
Err: err,
}
}
func main() {
err := errors.New("boom!")
err = Wrap(err, "main")
fmt.Println(err)
}
Nous verrons la sortie suivante:
Outputmain: boom!
WrappedError
est une structure avec deux champs: un message de contexte en tant questring
, et unerror
sur lequel ceWrappedError
fournit plus d'informations. Lorsque la méthodeError()
est invoquée, nous utilisons à nouveaufmt.Sprintf
pour imprimer le message de contexte, puis leerror
(fmt.Sprintf
sait appeler implicitement la méthodeError()
comme bien).
Dansmain()
, nous créons une erreur en utilisanterrors.New
, puis nous enveloppons cette erreur en utilisant la fonctionWrap
que nous avons définie. Cela nous permet d'indiquer que ceerror
a été généré en"main"
. De plus, puisque notreWrappedError
est aussi unerror
, nous pourrions envelopper d'autres `WrappedError`s - cela nous permettrait de voir une chaîne pour nous aider à retrouver la source de l'erreur. Avec un peu d'aide de la bibliothèque standard, nous pouvons même incorporer des traces de pile complètes dans nos erreurs.
Conclusion
Étant donné que l’interfaceerror
n’est qu’une méthode unique, nous avons vu que nous avons une grande flexibilité pour fournir différents types d’erreurs pour différentes situations. Cela peut englober tout, de la communication de plusieurs éléments d'information dans le cadre d'une erreur à l'implémentation deexponential backoff. Bien que les mécanismes de gestion des erreurs dans Go semblent à première vue sembler simplistes, nous pouvons réaliser une gestion relativement riche en utilisant ces erreurs personnalisées pour gérer les situations courantes et peu communes.
Go a un autre mécanisme pour communiquer un comportement inattendu, la panique. Dans notre prochain article de la série de gestion des erreurs, nous examinerons les paniques - ce qu'elles sont et comment les gérer.