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 string
Err 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 error
contextInfo 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
类型的错误,则获取失败的文件路径。如果不是这种类型的错误,则返回其他错误。