J'ai essayé de savoir comment modifier le traitement en fonction du type d'erreur dans Golang.
C'est parce que ** Golang ne prend pas en charge try-catch. ** ** J'ai donc commencé à écrire cet article lorsque j'ai essayé de découvrir comment Golang pouvait implémenter des fonctionnalités de type try-catch en Java.
À propos du plan général de l'article. -Tout d'abord, je vais expliquer quatre fonctions fréquemment utilisées du package d'erreur. -Ensuite, nous allons essayer trois méthodes possibles pour modifier le traitement en fonction du type d'erreur. -Et la conclusion de la meilleure façon de diviser le traitement en fonction du type d'erreur. Nous procéderons dans cet ordre.
Le package d'erreur standard n'a pas les fonctions suivantes.
--Déterminer le type d'erreur qui s'est produite en premier
Donc, fondamentalement, nous utilisons le package errors. Jetons un coup d'œil à la fonction de errors.
Il est utilisé lorsque ** génère simplement une erreur ** en le spécifiant dans la chaîne de caractères du message d'erreur comme indiqué ci-dessous.
err := errors.New("Ella~C'est vrai~Hmm")
fmt.Println("output: ",err)
// output:Ella~C'est vrai~Hmm
Il est utilisé lors de la génération d'une erreur ** en spécifiant le format ** et la chaîne de caractères du message d'erreur comme indiqué ci-dessous.
err := errors.Errorf("output: %s", "Ella~C'est vrai~Hmm")
fmt.Printf("%+v", err)
// output:Ella~C'est vrai~Hmm
Il est utilisé lors de l'encapsulation de l'erreur d'origine comme indiqué ci-dessous.
err := errors.New("repository err")
err = errors.Wrap(err, "service err")
err = errors.Wrap(err, "usecase err")
fmt.Println(err)
// usecase err: service err: repository err
** Fonctionnalité importante **, je vais donc l'expliquer un peu plus en détail. Par exemple, même si la hiérarchie est profonde, telle que couche de cas d'utilisation → couche de service → couche de référentiel En encapsulant la première erreur, vous pouvez amener les informations d'erreur inférieures au niveau supérieur. En conséquence, il est plus facile d'identifier la cause de l'erreur **. ** **
De plus, je m'en fiche cette fois, mais il semble qu'il soit courant d'inclure le nom de la fonction dans le message d'erreur afin d'identifier rapidement la cause. Comme les messages sont connectés les uns aux autres, il est également nécessaire de les assembler pour qu'il devienne un message d'erreur naturel.
Utilisé pour extraire le premier message d'erreur de l'erreur encapsulée. ** Très utile pour identifier la cause de la première erreur. ** **
err := errors.New("repository err")
err = errors.Wrap(err, "service err")
err = errors.Wrap(err, "usecase err")
fmt.Println(errors.Cause(err))
// repository err
Voici trois méthodes de gestion des erreurs. Voyons quel est le problème un par un et comment nous pouvons le résoudre.
--Méthode 1 Jugement par valeur d'erreur --Méthode 2 Jugement par type d'erreur --Méthode 3 Jugement en utilisant l'interface
En conclusion, je pense que la meilleure méthode est ** Jugement en utilisant l'interface de la méthode 3 **. (J'apprécierais si vous pouviez indiquer dans les commentaires s'il y a quelque chose comme ça!)
var (
//Définir les erreurs possibles
ErrHoge = errors.New("this is error hoge")
ErrFuga = errors.New("this is error fuga")
)
func Function(str string) error {
//Renvoie des erreurs différentes selon le processus
if str == "hoge" {
return ErrHoge
}else if str == "fuga" {
return ErrFuga
}
return nil
}
func main() {
err := Function("hoge")
switch err {
case ErrHoge:
fmt.Println("hoge")
case ErrFuga:
fmt.Println("fuga")
}
}
L'instruction switch détermine si la ** valeur de retour ** de la fonction est ErrHoge ou ErrFuga et distribue le traitement. ** Je pense que c'est une mauvaise façon **. Les problèmes sont les trois points suivants.
--Il est nécessaire de corriger le message d'erreur renvoyé par Function
Si les points ci-dessus posent des problèmes, vous devriez envisager d'autres méthodes.
type Err struct {
err error
}
func (e *Err) Error() string {
return fmt.Sprint(e.err)
}
type ErrHoge struct {
*Err
}
type ErrFuga struct {
*Err
}
func Function(str string) error {
//Renvoie des erreurs différentes selon le processus
if str == "hoge" {
return ErrHoge{&Err{errors.New("this is error hoge")}}
} else if str == "fuga" {
return ErrFuga{&Err{errors.New("this is error fuga")}}
}
return nil
}
func main() {
err := Function("hoge")
switch err.(type) {
case ErrHoge:
fmt.Println("hoge")
case ErrFuga:
fmt.Println("fuga")
}
}
L'instruction switch détermine si le ** type de retour ** de la fonction est ErrHoge ou ErrFuga et distribue le traitement. Cette méthode n'est pas non plus très bonne, mais comme le jugement de valeur est changé en jugement de type, Les problèmes suivants de ** Jugement par valeur d'erreur ** ont été résolus.
Il reste deux problèmes.
type temporary interface {
Temporary() bool
}
func IsTemporary(err error) bool {
te, ok := errors.Cause(err).(temporary)
return ok && te.Temporary()
}
type Err struct {
s string
}
func (e *Err) Error() string { return e.s }
func (e *Err) Temporary() bool { return true }
func Function(str string) error {
//Renvoie des erreurs différentes selon le processus
if str == "hoge" {
return &Err{"this is error"}
} else {
errors.New("erreur inattendue")
}
return nil
}
func main() {
err := Function("hoge")
if IsTemporary(err) {
fmt.Println("Erreur attendue:", err)
} else {
fmt.Println(err)
}
}
Le code est un peu compliqué, je vais donc l'expliquer. Err implémente l'interface temporaire. Par conséquent, il est possible de restreindre "celui qui implémente l'interface temporaire et la valeur de retour est vrai" par le jugement de ```IsTemporary () '' `` du traitement côté utilisateur.
De plus, en utilisant errors.cause comme indiqué ci-dessous, même si l'erreur est encapsulée, il est possible de distinguer ** si la première erreur s'est produite implémente l'interface temporaire **.
//Voir si l'erreur retournée implémente temporaire
te, ok := err.(temporary)
//Voir si la première cause de l'erreur implémente temporaire (← recommandé)
te, ok := errors.Cause(err).(temporary)
Par conséquent, en regardant le résultat de ```Est temporaire (err) `` `, vous pouvez trier le traitement en fonction de l'erreur (cause racine) qui s'est produite en premier.
Cette méthode a permis de résoudre les deux problèmes restants avec ** jugement de type d'erreur **.
Avec cela, si try-catch en Java est golang, il semble qu'il puisse être implémenté comme suit.
java
public static void main(String[] args) {
try {
// ...
} catch (ArithmeticException e) {
// ...
} catch (RuntimeException e) {
// ...
} catch (Exception e) {
// ...
}
}
golang
func main() {
err := Function("//....")
if IsArithmeticException(err) {
// ...
}
if IsRuntimeException(err) {
// ...
}
if IsException(err) {
// ...
}
// ...
}
J'ai expliqué la gestion de trois erreurs dans Golang. En faisant un jugement à l'aide de l'interface de la méthode 3, il semble que le traitement puisse être divisé selon le type d'erreur avec le moins de problèmes.
J'étudie toujours, alors j'apprécierais si vous pouviez me faire savoir dans les commentaires s'il existe d'autres moyens de gérer les erreurs.
Cet article a été très utile. https://dave.cheney.net/tag/error-handling
・ 10/11/2018 "La structure doit être exposée à l'extérieur" Cette description a été supprimée. En n'exposant pas la structure à l'extérieur, il est possible d'empêcher le jugement par type d'erreur, En effet, en faire une structure privée pose un problème en ce que la valeur du champ ne peut pas être référencée de l'extérieur.
Recommended Posts