错误类型
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 in 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() {
if r := recover(); r != nil {
fmt.Println("Recovered from:", r)
}
}()
fmt.Println("Start")
panic("Something went wrong!")
fmt.Println("End") // This line will not be executed
}
recover 通常用于确保某些代码块即使发生了 panic,也能执行资源清理操作,并避免整个程序崩溃。
defer
defer
语句用于延迟函数的执行,直到包含 defer 语句的函数执行完毕时,才会执行。这通常用于确保资源释放或清理操作(如关闭文件、解锁互斥锁等)即使在函数中发生错误或提前返回时也会被执行。
执行顺序
: 在一个函数中,你可以有多个 defer 语句。这些 defer 调用的执行顺序是后进先出(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