go语言处理错误的正确姿势

发布时间 2023-11-07 12:43:44作者: 技术颜良

Go语言处理错误的正确姿势

在Go语言中,错误处理是通过内置的error类型来实现的,而异常则是通过panicrecover函数来处理。

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

 

收录于合集 #go语言
 58
上一篇Go如何实现分布式锁下一篇Go语言实现时间窗口限流器
个人观点,仅供参考
阅读 429
程序员技术成长之路
 
关注公众号后可以给作者发消息