go语言处理错误的正确姿势
Go语言处理错误的正确姿势
在Go语言中,错误处理是通过内置的error类型来实现的,而异常则是通过panic和recover函数来处理。
Error
error是一个内置的接口类型,它的定义如下:
type error interface {Error() string}
这是一个简单的接口,只要你的类型实现了Error方法(这个方法会返回一个字符串),那么它就实现了error接口。在函数中,如果出现了错误情况,通常会返回一个实现了error接口的值。
Go语言的标准库中已经提供了几个实现了error接口的结构,例如errors.New函数返回的*errors.errorString类型,以及fmt.Errorf函数返回的*errors.errorString类型。
你也可以定义自己的错误类型,只要这个类型实现了error接口就可以。
type MyError struct {Message stringErr error}func (e *MyError) Error() string {return e.Message}
Exception
Go语言没有类似Java或者Python那样的异常机制,但是它提供了panic和recover两个函数,可以在遇到无法恢复的错误状态的时候,终止函数执行。简单来说,panic用于产生异常,而recover用于捕获异常。
panic函数接受一个实现了error接口的对象,或者是一个interface{}类型的对象。调用panic函数会立即停止函数的执行,并且运行defer语句。
recover函数用于捕获由panic函数引起的异常。recover函数只能在defer语句中使用。在正常执行过程中,调用recover会返回nil并且没有其他效果;但是如果当前的goroutine在恐慌中(已经调用过panic函数),调用recover可以捕获到panic的输入值,并且恢复正常的执行。
func main() {defer func() {if err := recover(); err != nil {fmt.Println("Recovered from", err)}}()panic("A severe error occurred: stopping the program!")}
在上面的例子中,当panic函数被调用(可能是由于一些严重的错误导致的)时,执行会立即停止,然后执行defer函数。在defer函数里面,我们调用了recover函数,捕获到了panic的输入值,之后函数就可以正常的执行下去了。
如何优雅的处理错误
Go语言鼓励开发人员对每个错误进行明确的处理。这是Go语言对异常处理的设计哲学,目标是在函数签名上让潜在的错误变得明显。这种方式鼓励开发者更早更显眼的注意到错误处理,而不是像某些语言那样抛出异常然后在别的地方处理。
优雅处理Error的一些方法包括:
-
使用errors包中的函数产生简单的纯错误文本信息。
-
使用fmt.Errorf生成错误信息,并包含有用的变量值。
-
创建自定义错误类型,可以包含更多信息,比如错误的上下文,使得错误更具有信息价值。
在Go中,错误被视为一等公民,并且Go鼓励你明确处理每一个可能的错误。虽然这可能会让代码看起来有些繁琐,但是这也会让你的应用更加稳健,因为你很难忽略处理错误。
一、定义返回错误
func LowerLevelFunction() (string, error) {// 执行一些操作return result, err}func HigherLevelFunction() {result, err := LowerLevelFunction()if err != nil {// 处理错误} else {// 使用结果}}
实际上,Go语言中的错误处理就是这样简单直接。你需要在你的函数中返回一个错误,并在接收到错误时处理它。几乎所有的 return 语句都包含了一个错误值,这就是 Go 语言让错误处理显而易见的方式。
二、封装错误继续抛
_, err := os.Open("non-existing-file")if err != nil {return fmt.Errorf("failed to open file: %w", err)}
上述代码中的%w标志符在Go 1.13版本被引入,它允许将原始错误包装在新的错误中。
你也可以创建自定义错误类型,提供更多上下文信息。例如:
type customError struct {originalError errorcontextInfo string}func (ce customError) Error() string {return fmt.Sprintf("failed: %s: %v", ce.contextInfo, ce.originalError)}_, err := os.Open("non-existing-file")if err != nil {return customError{originalError: err,contextInfo: "failed to open file",}}
在这个例子中,customError包括了原始的错误和额外的上下文信息。并且,customError实现了Error()方法,使其满足error接口。
总的来说,包装错误并向上抛出可以帮助我们提供更多的错误上下文,以便于更好地排查问题。在Go 1.13及以上的版本,我们还可以使用errors.Is和errors.As去检查和获取原始的错误。
errors.Is函数用于检测一个错误是否与特定的错误相同。它会考虑错误的原始类型和通过fmt.Errorf包装的错误。
_, err := os.Open("non-existing-file")if errors.Is(err, os.ErrNotExist) {fmt.Println("file does not exist")} else {fmt.Println("other error")}
以上的代码,如果打开文件的错误是因为文件不存在(os.ErrNotExist),则返回文件不存在的错误信息,否则返回其他错误。
errors.As函数用于将错误转为特定的错误类型,如果转换失败,将返回 false。
_, err := os.Open("non-existing-file")if pathError, ok := err.(*os.PathError); ok {fmt.Println("failed at path:", pathError.Path)} else {fmt.Println("other error")}
以上的代码,如果打开文件的错误是*os.PathError类型的错误,则获取失败的文件路径。如果不是这种类型的错误,则返回其他错误。

浙公网安备 33010602011771号