Maison  >  Article  >  développement back-end  >  Comment gérer les erreurs dans Golang

Comment gérer les erreurs dans Golang

青灯夜游
青灯夜游original
2022-12-23 11:08:265825parcourir

Golang dispose généralement de trois méthodes de gestion des erreurs : sentinelle d'erreur (Sentinel Error), assertion de type d'erreur et enregistrement de la pile d'appels d'erreur. La sentinelle d'erreur fait référence à l'utilisation d'une variable avec une valeur spécifique comme condition de jugement pour la branche de traitement des erreurs. Les types d'erreur sont utilisés pour acheminer la logique de gestion des erreurs et ont le même effet que les sentinelles d'erreur. Le système de types fournit l'unicité des types d'erreur. La boîte noire d'erreur fait référence au fait de ne pas prêter trop d'attention au type d'erreur et de renvoyer l'erreur à la couche supérieure ; lorsqu'une action doit être prise, des assertions doivent être faites sur le comportement de l'erreur plutôt que sur le type d'erreur.

Comment gérer les erreurs dans Golang

L'environnement d'exploitation de ce tutoriel : système Windows 7, GO version 1.18, ordinateur Dell G3.

golang ne fournit pas de mécanisme de gestion des erreurs similaire à try-catch. Il adopte la gestion des erreurs de style langage C au niveau de la conception et renvoie les informations d'erreur via les valeurs de retour de la fonction. try-catch类似的错误处理机制,在设计层面采用了C语言风格的错误处理,通过函数返回值返回出错的错误信息,具体样例如下:

func ReturnError() (string, error) {
	return "", fmt.Errorf("Test Error")
}

func main() {
	val, err := ReturnError()
	if err != nil {
		panic(err)
	}
	fmt.Println(val)
}

上面的例子是一个基本的错误处理样例,生产环境中执行的调用栈往往非常复杂,返回的error也各式各样,常常需要根据返回的错误信息确定具体的错误处理逻辑。

Golang通常有如下的三种错误处理方式,错误哨兵(Sentinel Error)、错误类型断言(Error Type Asseration)和记录错误调用栈。

错误哨兵(Sentinel Error)

哨兵指的是用特定值的变量作为错误处理分支的判定条件,常见的应用场景有gorm中的gorm.RecordNotFounded和redis库里的redis.NIL

golang里可以对同类型变量进行比较,接口变量则比较接口指向的的指针的地址。因此,当且仅当error类型的变量指向同一地址时,此两种变量相等,否则都为不相等。

var ErrTest = errors.New("Test Error")

err := doSomething()
if err == ErrTest{
	// TODO: Do With Error
}

使用哨兵存在如下几个问题存在两个问题:

1、代码结构不灵活,分支处理只能使用==或者!=进行判定,长此以往,容易写出常意大利面条式的代码。

var ErrTest1 = errors.New("ErrTest1")
var ErrTest2 = errors.New("ErrTest1")
var ErrTest3 = errors.New("ErrTest1")
……
var ErrTestN = errors.New("ErrTestN")
……
if err  == ErrTest1{
	……
} else if err == ErrTest2{
	……
}else if err == ErrTest3{
	……
}
……
else err == ErrTestN{
	……
}

2、哨兵变量值不能被修改,否则会导致逻辑错误,上述golang写法的error哨兵可以被改变,可以通过如下方式解决:

type Error string

func (e Error) Error() string { return string(e) }

3、哨兵变量会导致极强的耦合性,接口新增error的吐出就会造成使用者相应修改代码新增的处理错误问题。

相比较上面的方案,错误哨兵还有一种更为优雅的方案,依赖于接口而非变量:

var ErrTest1 = errors.New("ErrTest1")

func IsErrTest1(err error) bool{
  return err == ErrTest1
}

错误类型

错误类型来路由错误处理逻辑,和错误哨兵有异曲同工的作用,由类型系统来提供错误种类的唯一性,使用方法如下:

type TestError {
}
func(err *TestError) Error() string{
	return "Test Error"
}
if err, ok := err.(TestError); ok {
	//TODO 错误分支处理
}

err := something()
switch err := err.(type) {
case nil:
        // call succeeded, nothing to do
case *TestError:
        fmt.Println("error occurred on line:", err.Line)
default:
// unknown error
}

相比较于哨兵,错误类型的不变性更好,且可以使用switch来提供优雅的路由策略。但是这使得使用方依旧无法避免对于包的过重依赖。

使用接口抛出更复杂,多样的错误,依旧需要改变调用方的代码。

错误黑盒(依赖错误接口)

错误黑盒指的是不过分关心错误类型,将错误返回给上层。当需要采取行动时,要针对错误的行为进行断言,而非错误类型。

func fn() error{
	x, err := Foo()
	if err != nil {
		return err
	}
}

func main(){
	err := fn()
	if IsTemporary(err){
		fmt.Println("Temporary Error")
	}
}

type temporary interface {
        Temporary() bool
}
 
// IsTemporary returns true if err is temporary.
func IsTemporary(err error) bool {
        te, ok := err.(temporary)
        return ok && te.Temporary()
}

通过这样的方式,1.直接就解耦了接口间的依赖,2. 错误处理路由和错误类型无关,而与具体行为有关,避免了膨胀的错误类型。

总结

错误哨兵和错误类型避免不了依赖过重的问题,只有错误黑盒能够将问题从确定错误类型(变量)的处理逻辑变为确定错误行为。因此推荐使用第三种方式来处理错误。

这里必要要加一句,黑盒处理,返回错误并不意味着对错误的存在不理会或者是直接忽略,而是需要在合适的地方优雅得处理。在这个过程中,可以通过errorsWrap

func authenticate() error{
	return fmt.Errorf("authenticate")
}

func AuthenticateRequest() error {
	err := authenticate()
	// OR logger.Info("authenticate fail %v", err)
	if err != nil {
		return errors.Wrap(err, "AuthenticateRequest")
	}
	return nil
}

func main(){
	err := AuthenticateRequest()
	fmt.Printf("%+v\n", err)
	fmt.Println("##########")
	fmt.Printf("%v\n", errors.Cause(err))
}

// 打印信息
authenticate
AuthenticateRequest
main.AuthenticateRequest
	/Users/hekangle/MyPersonProject/go-pattern/main.go:17
main.main
	/Users/hekangle/MyPersonProject/go-pattern/main.go:23
runtime.main
	/usr/local/Cellar/go@1.13/1.13.12/libexec/src/runtime/proc.go:203
runtime.goexit
	/usr/local/Cellar/go@1.13/1.13.12/libexec/src/runtime/asm_amd64.s:1357
##########
authenticate
L'exemple ci-dessus est un exemple de base de gestion des erreurs. La pile d'appels exécutée dans un environnement de production est souvent très complexe, et les erreur renvoyées sont également diverses. Il est souvent nécessaire de déterminer l'erreur spécifique. message basé sur les informations d’erreur renvoyées. Logique de gestion des erreurs.

Golang dispose généralement des trois méthodes de gestion des erreurs suivantes : la sentinelle d'erreur (Sentinel Error), l'assertion de type d'erreur (Error Type Asseration) et l'enregistrement de la pile d'appels d'erreur.

Error Sentinel (Sentinel Error)

Sentinel fait référence à l'utilisation d'une variable avec une valeur spécifique comme condition de jugement de la branche de traitement des erreurs Les scénarios d'application courants incluent gorm.RecordNotFounded dans gorm et redis.NIL dans la bibliothèque redis. En golang, les variables du même type peuvent être comparées et les variables d'interface comparent les adresses des pointeurs pointés par l'interface. Donc, si et seulement si la variable de type error pointe vers la même adresse, les deux variables sont égales, sinon elles ne sont pas égales. rrreeeIl y a deux problèmes avec l'utilisation de Sentinel : 🎜🎜1 La structure du code n'est pas flexible et le traitement des branches ne peut être déterminé qu'en utilisant == ou != , à long terme, il est facile d'écrire du code de type spaghetti. 🎜rrreee🎜2. La valeur de la variable sentinelle ne peut pas être modifiée, sinon cela provoquera une erreur logique. La sentinelle d'erreur dans la méthode d'écriture golang ci-dessus peut être modifiée, ce qui peut être résolu de la manière suivante : 🎜rrreee🎜3. La variable sentinelle conduira à un couplage extrêmement fort, l'interface La génération de nouvelles erreurs amènera les utilisateurs à modifier le code en conséquence et à ajouter de nouveaux problèmes d'erreur de traitement. 🎜🎜Par rapport à la solution ci-dessus, Error Sentinel propose une solution plus élégante qui s'appuie sur des interfaces plutôt que sur des variables : 🎜rrreee

Type d'erreur

🎜Types d'erreur logique de traitement des erreurs d'itinéraire, qui a le même effet que les sentinelles d'erreurs. Le système de types fournit l'unicité des types d'erreurs. La méthode d'utilisation est la suivante : 🎜rrreee🎜Par rapport aux sentinelles, les types d'erreurs sont plus invariants et peuvent utiliser . switch pour fournir des stratégies de routage élégantes. Mais cela empêche les utilisateurs d’éviter une dépendance excessive à l’égard des packages. 🎜🎜Utiliser des interfaces pour générer des erreurs plus complexes et diverses nécessite toujours de changer le code de l'appelant. 🎜

Boîte noire d'erreur (dépend de l'interface d'erreur)

🎜La boîte noire d'erreur fait référence au fait de ne pas trop se soucier du type d'erreur et renvoyer l'erreur à la couche supérieure. Lorsqu’une action est requise, faites des affirmations sur le comportement de l’erreur, et non sur le type d’erreur. 🎜rrreee🎜De cette façon, 1. Les dépendances entre les interfaces sont directement découplées, 2. Le routage de gestion des erreurs n'a rien à voir avec les types d'erreurs, mais est lié à des comportements spécifiques, évitant ainsi l'expansion des types d'erreurs. 🎜

Résumé

🎜Les sentinelles d'erreur et les types d'erreur ne peuvent pas éviter le problème de dépendance excessive. Seule la boîte noire d'erreur peut résoudre le problème de la détermination du type d'erreur (. variable) La logique de traitement change pour déterminer le comportement d'erreur. Il est donc recommandé d’utiliser la troisième méthode pour gérer les erreurs. 🎜🎜Il est nécessaire d'ajouter ici, Traitement de la boîte noire, renvoi d'erreurs ne signifie pas ignorer l'existence d'erreurs ou les ignorer directement, mais cela doit être traité avec élégance à l'endroit approprié. Dans ce processus, vous pouvez transmettre le Wrap des erreurs🎜,🎜 Zap🎜logging et d'autres méthodes enregistrent les informations contextuelles du lien appelant pendant le processus de retour d'erreur couche par couche. 🎜rrreee🎜【Recommandations associées : 🎜Tutoriel vidéo Go🎜, 🎜Enseignement de la programmation🎜】🎜

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn