Golang error的个人理解 | 青训营笔记

155 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记。

最近几节课下来,跟着老师们写了几个小项目,虽然熟悉了Go的语法和一些常见标准包的使用,项目的大体流程也能够理解,但是由于课程还是比较硬核,一些细节还是没有掌握,比如error的使用,虽然能够照猫画虎地用起来,但是要优雅地用还是得认真梳理一下。

Golangerror的个人理解

1. 使用场景

Golang不像Java使用throw->(try, catch, finally)来处理Checked Exception,在Go看来,把能够预见的“异常”都视作普通错误,开发者可以通过特定的代码解决这些可预见的错误,这些错误通常不会引起程序的崩溃。而数组越界、除数为0这种会引起程序崩溃的错误在Go中则被认为是真正的异常,需要使用panicrecovery来处理。因此,在Go中使用多值返回来进行普通错误的捕获和处理。例如:打开一个文件时可能出现“文件不存在”的情况,我们就可以通过返回的error来进行流程控制。通过这种方式,一定程度上实现了代码的简洁优雅,不会像Java等语言一样出现Exception的滥用。

// 在某些情况下我们需要通过文件来进行一些工作
open, err := os.Open("test.txt")
// 但是如果出现一些错误就无法进行后续操作
if err != nil {
    // 错误处理
}

2. error的结构

首先,需要明白error是一个类型(接口)!

errorint, byte, bool等数据类型一样,定义在内置库builtin中,它是表示错误的常规接口,nil代表没有错误。因此,我们可以实现这个接口来实现自定义的error

// 官方文档中对error的定义
// 内置的error接口类型是表示错误的常规接口,nil代表无错误
type error interface {
    Error() string
}

我们也可以通过这个接口自定义error

type MyError struct {
    ErrorTime time.Time
    Msg string
}
// 实现error接口中的Error方法
func (myError *MyError) Error() string {
    return myError.Msg
}

3. errors标准库(包)

需要与error区别开,errors是一个标准库(包)!

errors包中实现了4个用于操作error的函数,包括

  • errors.New(s string) error: 创建一个错误。

  • errors.Unwrap(err error) error: 从错误链中拆解错误。

  • errors.Is(err, target error) bool: 判断错误链中是否存在与target匹配的错误。特别的,如果该错误与target相等,或者它实现了一个方法is(error) bool,使is(target)返回true,则认为该错误与target匹配。

  • errors.As(err error, target any) bool: 查找错误链中与target匹配的第一个错误,如果存在则将该错误值赋值给target并返回true,否则返回false。特别的,如果错误的具体值可以分配给target所指向的变量,或者如果该错误有一个方法As(interface{}) bool,使得As(target)返回true,那么该错误就与target匹配。

需要注意的是,拆解错误的函数在errors包中而嵌套错误则需要使用fmt.Errorf(“%w”, err) error,其中w可以理解为wrap来方便记忆使用。

4. error的基本使用

  • 创建
    • errors.New(s string) error
    // 使用举例
    err := errors.New("This is an error!")
    
    • fmt.Errorf(s string) error
    // 使用举例
    err := fmt.Errorf("This is an error!")
    
  • 嵌套(将一个error关联到一条错误链中)
    • fmt.Errorf("%w", err) error
    // 使用举例
    err1 := errors.New("error-1")
    err2 := fmt.Errorf("error-2 [%w]", err1)
    // 打印一下两个err看看
    fmt.Printf("The type of err1: %T\n", err1)
    fmt.Printf("The content of err1: %v\n", err1)
    fmt.Printf("The type of err2: %T\n", err2)
    fmt.Printf("The content of err2: %v\n", err2)
    // output:
    // The type of err1: *errors.errorString
    // The content of err1: error-1
    // The type of err2: *fmt.wrapError
    // The content of err2: error-2 [error-1]
    
  • 拆解(将一个error从错误链中拆解下来)
    • errors.Unwrap(err error) error
        // 使用举例
        err1 := errors.New("error-1")
        err2 := fmt.Errorf("error-2 [%w]", err1)
        fmt.Println(err2)
        // 再拆解
        err := errors.Unwrap(err2)
        fmt.Printf("Unwrap once: %v\n", err)
        err = errors.Unwrap(err)
        fmt.Printf("Unwrap twice: %v\n", err)
        // output:
        // error-2 [error-1]
        // Unwrap once: error-1
        // Unwrap twice: <nil>
        
        // 通过第二次拆解可以看出:
        // 对单个error拆解后返回nil
    
  • 判断是否存在匹配
    • errors.Is(err, target error) bool
    // 使用举例
    err1 := errors.New("error-1")
    err2 := fmt.Errorf("error-2 [%w]", err1)
    err3 := fmt.Errorf("error-3 [%w]", err2)
    fmt.Println(err3)
    // 判断错误链err3中是否存在与err1匹配的错误
    fmt.Printf("err1 in err3: %v\n", errors.Is(err3, err1)) 
    // output:
    // error-3 [error-2 [error-1]]
    // err1 in err3: true
    
  • 查找第一个匹配并赋值
    • errors.As(err error, target any) bool
    // 定义一个自定义的错误
    type MyError struct {
       ErrorTime time.Time
       Msg       string
       Err       error
    }
    // 实现error接口中的Error()方法
    func (myErr MyError) Error() string {
        timeStr := myErr.ErrorTime.Format("2006-01-02 15:04:05")
        return timeStr + " " + myErr.Msg
    }
    
    // 使用举例
    err1 := errors.New("error-1")
    err2 := fmt.Errorf("error-2 [%w]", err1)
    err3 := fmt.Errorf("error-3 [%w]", err2)
    var t1 any
    // any和一切类型匹配,所以第一个输出应该为true
    fmt.Println(errors.As(err3, &t1), t1)
    var t2 error
    // error和error匹配,所以第一个输出也为true
    fmt.Println(errors.As(err3, &t2), t2)
    var t3 MyError
    // MyError和error无法匹配,所以第一个输出为false
    fmt.Println(errors.As(err3, &t3), t3)
    // 定义一个MyError类型实例
    myError := MyError{
       ErrorTime: time.Now(),
       Msg:       "DIY error",
    }
    // MyError和MyError匹配,所以第一个输出为true
    fmt.Println(errors.As(myError, &t3), t3)
    // output:
    // true error-3 [error-2 [error-1]]
    // true error-3 [error-2 [error-1]]
    // false 0001-01-01 00:00:00 
    // true 2022-05-12 02:29:16 DIY error