[Gestion des erreurs Golang] La meilleure façon de diviser le traitement en fonction du type d'erreur

introduction

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.

4 fonctions fréquemment utilisées du package d'erreurs

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.

Fonction 1 func Nouvelle erreur (chaîne de message)

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

Erreur de fonction 2 func Errorf (chaîne de format, args ... interface {})

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

Fonction 3 func Wrap (erreur d'erreur, chaîne de message)

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.

Fonction 4 func Cause (erreur d'erreur) Erreur

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

Rechercher la meilleure gestion des erreurs

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!)

Méthode 1 Jugement par valeur d'erreur

code

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")
	}
}

Résumé

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.

Méthode 2 Jugement par type d'erreur

code

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")
	}
}

Résumé

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.

Méthode 3 Jugement à l'aide de l'interface

code

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.

Résumé

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) {
		// ...
	}
	// ...
}

finalement

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

Postscript

・ 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

[Gestion des erreurs Golang] La meilleure façon de diviser le traitement en fonction du type d'erreur
Un moyen simple de mesurer la vitesse de traitement d'un disque reconnu par Linux
Introduction à l'apprentissage automatique ~ Montrons le tableau de la méthode du K plus proche voisin ~ (+ gestion des erreurs)
Un moyen simple de vérifier la source des modules Python
Essayez d'obtenir le contenu de Word avec Golang
La musique de fond est automatiquement sélectionnée en fonction du contenu de la conversation
Changer la valeur de paramètre de setting.py en fonction de l'environnement de développement
Comment augmenter la vitesse de traitement de l'acquisition de la position des sommets
Gestion des erreurs après l'arrêt du téléchargement des données apprises de VGG16
Changer le volume de Pepper en fonction de l'environnement environnant (son)
Point selon l'image
Considérez la vitesse de traitement pour déplacer le tampon d'image avec numpy.ndarray