开发者

Go语言标准错误error全面解析

目录
  • 错误类型
    • 新建错误
    • 错误解析
    • 错误处理
  • 总结

    错误类型

    • errorString

    错误是程序中处理逻辑和系统稳定新的重要组成部分。

    在go语言中内置错误如下:

    // The error built-in interface type is the conventional interface for// representing an error condition, with the nil value representing no error.type error interface {    Error() string}

    error类型是一个接口类型,内含一个Error的方法。它是错误的顶级接口,实现了此内置方法的结构体都是其子类。

    errorString结构体是内置实现错误接口的内置实现,源码如下:

    // New returns an error that formats as the given text.
    // Each call to New returns a distinct error value even if the text is identical.
    func New(text string) error {
    	return &errorString{text}
    }
    
    // errorString is a trivial implementation of error.
    type errorString struct {
    	s string
    }
    
    func (e *errorString) Error() string {
    	return e.s
    }

    New方法是内置错误类型实现。

    errors.New()是最常用的错误类实现方法。

    • wrapError

    wrapError是error的另一种实现类,位于fmt的包中,源码如下:

    // Errorf formats according to a format specifier and returns the string as a
    // value that satisfies error.
    //
    // If the format specifier includes a %w verb with an error operand,
    // the returned error will implement an Unwrap method returning the operand.
    // If there is more than one %w verb, the returned error will implement an
    // Unwrap method returning a []error containing all the %w operands in the
    // order they appear i编程客栈n the arguments.
    // It is invalid to supply the %w verb with an operand that does not implement
    // the error interface. The %w verb is otherwise a synonym for %v.
    func Errorf(format string, a ...any) error {
    	p := newprinter()
    	p.wrapErrs = true
    	p.doPrintf(format, a)
    	s := string(p.buf)
    	var err error
    	switch len(p.wrappedErrs) {
    	case 0:
    		err = errors.New(s)
    	case 1:
    		w := &wrapError{msg: s}
    		w.err, _ = a[p.wrappedErrs[0]].(error)
    		err = w
    	default:
    		if p.reordered {
    			slices.Sort(p.wrappedErrs)
    		}
    		var errs []error
    		for i, argNum := range p.wrappedErrs {
    			if i > 0 && p.wrappedErrs[i-1] == argNum {
    				continue
    			}
    			if e, ok := a[argNum].(error); ok {
    				errs = append(errs, e)
    			}
    		}
    		err = &wrapErrors{s, errs}
    	}
    	p.free()
    	return err
    }
    
    type wrapError struct {
    	msg string
    	err error
    }
    
    func (e *wrapError) Error() string {
    	return e.msg
    }
    
    func (e *wrapError) Unwrap() error {
    	return e.err
    }

    wrapError是另一个内置错误实现类,使用%w作为占位符,这里的wrapError实现了Unwrap方法,用户返回内置的err即嵌套的err。

    wrapError还有一个复数形式wrapErrors这里不再过多赘述。

    • 自定义错误

    实现自定义错误非常简单,面向对象的特性实现错误接口erros就是实现了错误类。安装go语言的继承的特性,实现接口对应的方法即可。

    type error interface {
    	Error() string
    }
    type MyErr struct {
    	e string
    }
    
    func (s *MyErr) Error() string {
    	return s.e
    }
    
    
    func main(){
    	var testErr error
    	testErr = &MyErr{"err"}
    	fmt.Println(testErr.Error())	
    }

    上述代码就是实现了一个自定义的error类型,注意它是一个结构体,实际上是errorString的子类。

    新建错误

    上一小节介绍了三种错误类型,前两中是内置的错误类型,其中自定义的错误是可拓展的,可以实现前两种的任意一个。

    第一种errorString是实现比较方便,只有实现Error()方法;

    第二种是wrapError需要实现两种方法,还有一种是Unwrap()

    • errors.New()
    err := errors.New("this is a error")
    fmt.Printf("----%T----%v\n", err, err)

    该方法创建的是errorString类实例

    • fmt.Errorf()
    err = fmt.Errorf("err is: %v", "no found")
    fmt.Println(err)

    该方法创建的是wrapError类实例,wrapError也是errorString的子类。

    • 实例化
    type MyErr struct {
    	e string
    }
    
    func (s *MyErr) Error() string {
    	return s.e
    }
    
    
    func main(){
    	var testErr error
    	testErr = &MyErr{"err"}
    	fmt.Println(testErr.Error())
    }

    由于自定义错误一般需要错误信息,所以一般直接构造方法实例化。

    错误解析

    • errors.Is()

    errors.Is 用于判断一个错误是否与另一个特定的错误相等。它不仅仅是简单的比较错误的值,还会检查错误链中的所有错误,看看它们是否与给定的目标错误匹配。

    package main
    
    import (
        "errors"
        "fmt"
    )
    
    var ErrNotFound = errors.New("not found")
    
    func findItem(id int) error {
        if id == 0 {
            return ErrNotFound
        }
        return nil
    }
    
    func main() {
        err := findItem(0)
        if errors.Is(err, ErrNotFound) {
            fmt.Println("Item not found")
        } else {
            fmt.Println("Item found")
        }
    }
    

    注意这里有一个坑,就是Is方法判断的错误的类型和错误的信息,使用New方法即使构建的错误信息相同类型不一样也是不相等的,如下:

    err1 := errors.New("err1")
    err2 := errors.New("err1")
    err := errors.Is(err1, err2)
    fmt.Println(err) // 输出: false
    • errors.As()

    errors.As 用于将一个错误转换为特定的错误类型。如果错误链中的某个错误匹配给定的目标类型,那么 errors.As 会将该错误转换为该类型,并将其赋值给目标变量。

    package main
    
    import (
        "errors"
        "fmt"
    )
    
    type MyError struct {
        Code int
        Msg  string
    }
    
    func (e *MyError) Error() string {
        return fmt.Sprintf("code %d: %s", e.Code, e.Msg)
    }
    
    func DOSomething() error {
        return &MyError{Code: 404, Msg: "Not Found"}
    }
    
    func main() {
        err := doSomething()
        var myErr *MyError
        if errors.As(err, &myErr) {
            fmt.Printf("Custom error: %v (Code: %d)\n", myErr.Msg, myErr.Code)
        } else {
            fmt.Println("Unknown error")
        }
    }
    

    可用作类型判断。

    • errors.Unwrap()

    errors.Unwrap() 是一个用于处理嵌套或包装错误的函数。它的主要作用是提取并返回一个错误的直接底层错误(即被包装的错误),如果该错误没有被包装过,则返回 nil

    package main
    
    import (
    	"errors"
    	"fmt"
    )
    
    func main() {
    	baseErr := errors.New("base error")
    	wrappedErr := fmt.Errorf("wrapped error: %w", baseErr)
    
    	// 使用 errors.Unwrap 来提取底层错误
    	unwrappedErr := errors.Unwrap(wrappedErr)
    	fmt.Println("Wrapped error:", wrappedErr)
    	fmt.Println("Unwrapped error:", unwrappedErr)
    }
    
    
    // Output
    Wrapped error: wrapped error: base error
    Unwrapped error: base error

    该方法只能获取错误的直接内嵌错误,如果要获取更深层次的错误需要便利判断。

    注意:

    • errors.Is: 用于判断一个错误是否与特定的错误值相等。适合在错误链中查找某个特定的错误(比如一个已知的预定义错误)。
    • errors.As: 用于将错误转换为特定的错误类型。适合当你需要根据错误的具体类型来处理错误时使用。
    • errors.Unwrap() 用于从一个包装错误中提取并返回底层的错误。如果错误没有被包装过(或者没有实现 Unwrap 方法),它会返回 nil

    错误处理

    if err := findAll(); err != nil {
    	// logic
    }

    这个处理过程是不是很眼熟,利用错误解析小节的处理方法可以对错误判断,进行后续的处理。

    go语言也提供了错误捕获recover的机制和错误抛出panic机制。

    • panic

    panic 是 Go 语言中的一种触发异常处理机制的方式。当你调用 panic 时,程编程序会立刻停止执行当前函数,并从调用栈中逐层向上抛出,直到找到一个适合的 recover,或者最终导致程序崩溃。

    package main
    
    import "fmt"
    
    func main() {
        fmt.Println("Start")
        panic("Something went wrong!")
        fmt.Println("End") // This line will not be executed
    }

    当程序中调用了 panic,程序会立刻中止当前的控制流,开始回溯帧,并执行每一层的 defer 语句。在执行完所有的 defer 语句后,如果没有遇到 recover,程序将崩溃,并打印 panic 的信息和堆栈跟踪。

    • recover

    recover 是一个内建函数,用于恢复 panic。它只能在 defer 函数中有效。当 panic 发生时,如果当前函数任何调用栈上的函数中有 defer 函数调用了 recover,那么可以捕获 panic 的内容,并使程序恢复正常执行。

    package main
    
    import "fmt"
    
    func main() {
        defer func()js {
            if r := recover(); r != nil {
                fmt.Println("Recovered from:", r)
            }
        }()
        fmt.Println("Start")
       js panic("Something went wrong!")
        fmt.Println("End") // This line will not be executed
    }
    

    recover 通常用于确保某些代码块即使发生了 panic,也能执行资源清理操作,并避免整个程序崩溃。

    • defer

    defer 语句用于延迟函数的执行,直到包含 defer 语句的函数执行完毕时,才会执行。这通常用于确保资源释放或清理操作(如关闭文件、解锁互斥锁等)即使在函数中发生错误或提前返回时也会被执行。

    执行顺序: 在一个函数中,你可以有多个 defer 语句。这些 defer 调用的执行顺http://www.devze.com序是后进先出(LIFO)。也就是说,最后一个 defer 声明的语句会最先执行。

    捕获值: defer 语句会在声明时捕获它引用的变量的值。也就是说,defer 语句中的参数会在声明 defer 时计算,而不是在 defer 执行时计算。

    三个函数组合构成了错误处理,如下:  

    package main
    
    import "fmt"
    
    func main() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Println("Recovered from panic:", r)
            }
        }()
    
        fmt.Println("Starting the program")
        causePanic()
        fmt.Println("Program ended gracefully")
    }
    
    func causePanic() {
        fmt.Println("About to cause panic")
        panic("A severe error occurred")
        fmt.Println("This line will not execute")
    }
    
    Starting the program
    About to cause panic
    Recovered from panic: A severe error occurred

    总结

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜