上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
4.3 错误处理
没有结构化异常,以返回值标记错误状态。
// builtin type error interface { Error() string } // io func ReadAll(r Reader) ([]byte, error)
- 标志性错误 :一种明确状态,比如
io.EOF
。 - 提示性错误 :返回可读信息,提示错误原因。
必须检查所有返回错误,这也导致代码不太美观。
func main() { file, err := os.Open("./main.go") if err != nil { log.Fatalln(err) } defer file.Close() content, err := io.ReadAll(file) if err != nil { log.Fatalln(err) } fmt.Println(string(content)) }
标志性错误,通常以 全局变量 (指针、接口)方式定义。而提示性错误,则直接返回 临时对象 。
// io var EOF = errors.New("EOF")
// src/errors/errors.go package errors func New(text string) error { return &errorString{text} // pointer & interface } type errorString struct { s string } func (e *errorString) Error() string { return e.s }
- 是否有错:
err != nil
- 具体错误:
err == ErrVar
- 错误匹配:
case ErrVal
- 类型转换:
e, ok := err.(T)
自定义承载更多上下文的错误类型。
type TestError struct { x int } func (e *TestError) Error() string { return fmt.Sprintf("test: %d", e.x) } var ErrZero = &TestError{ 0 } // 指针! (以便判断是否同一对象) // ----------------------------- func main() { var e error = ErrZero fmt.Println(e == ErrZero) if t, ok := e.(*TestError); ok { fmt.Println(t.x) } }
函数返回时,须注意 error
是否真等于 nil
,以免错不成错。
func test() error { var err *TestError // 结构体指针。 println(err == nil) // true // 接口只有类型和值都为 nil 时才等 // 于 nil。err 显然有类型信息。正 // 确做法是直接 return nil。 // 转型为接口。 return err } func main() { err := test() println(err == nil) // false }
标准库:
errors.New
: 创建包含文本信息的错误对象。errors.Join
: 打包一到多个错误对象,构建树状结构。fmt.Errorf
: 以%w
包装一到多个错误对象。
包装对象:
errors.Unwrap
: 返回被包装错误对象(或列表)。errors.Is
: 递归查找是否有指定错误对象。errors.As
: 递归查找并获取类型匹配的错误对象。
创建错误树,返回完整的错误信息。
func database() error { return errors.New("data") } func cache() error { if err := database(); err != nil { return fmt.Errorf("cache miss: %w", err) } return nil } func handle() error { return cache() } func main() { fmt.Println(handle()) } // cache miss: data
从错误树获取信息。
type TestError struct {} func (*TestError) Error() string { return "" } func main() { // 打包。 a := errors.New("a") b := fmt.Errorf("b, %w", a) c := fmt.Errorf("c, %w", b) fmt.Println(c) // c, b, a fmt.Println(errors.Unwrap(c) == b) // true // 递归检查。 fmt.Println(errors.Is(c, a)) // true // ------------------------------- x := &TestError{} y := fmt.Errorf("y, %w", x) z := fmt.Errorf("z, %w", y) // 提取(二级指针)类型匹配的错误对象。 var x2 *TestError if errors.As(z, &x2) { fmt.Println(x == x2) // true } }
恐慌
与返回 error
相比, panic
/ recover
的使用方式,很像结构化异常。
func panic(v any) func recover() any
恐慌 ( panic
)立即中断当前流程,执行延迟调用。
在延迟调用中, 恢复 ( recover
) 捕获并返回恐慌数据。
- 沿调用堆栈向外传递,直至被捕获,或进程崩溃。
- 连续引发恐慌,仅最后一次可被捕获。
- 先捕获恐慌,恢复执行,然后才返回数据。
- 恢复之后,可再度引发恐慌,可再次捕获。
- 无论恢复与否,延迟调用总会执行。
- 延迟调用中引发恐慌,不影响后续延迟调用执行。
func main() { defer func() { // 拦截恐慌,返回数据。 // 数据未必是 error,也可能是 nil。 // 无法回到 panic 后续位置继续执行。 if r := recover(); r != nil { log.Fatalln(r) } }() func() { panic("p1") // 终止当前函数,执行 defer。 }() println("exit.") // 即便 recover,也不再执行。 } // p1
func main() { defer func() { // 只有最后一次被捕获。 if err := recover(); err != nil { log.Fatalln(err) } }() func() { defer func(){ panic("p2") // 第二次。 }() panic("p1") // 第一次。 }() println("exit.") } // p2
func main() { defer func() { log.Println(recover()) // 捕获第二次:p2 }() func() { defer func(){ panic("p2") // 第二次! }() defer func() { log.Println(recover()) // 捕获第一次:p1 }() panic("p1") // 第一次! }() println("exit.") } // p1 // p2
恢复函数 recover
只能在延迟调用(topmost)内正确执行。
直接注册为延迟调用,或被延迟函数间接调用都无法捕获恐慌。
func catch() { recover() } func main() { defer catch() // 有效!在延迟函数内直接调用。 // defer log.Println(recover()) // 无效!作为参数立即执行。 // defer recover() // 无效!被直接注册为延迟调用。 panic("p!") }
以 *PanicNilError
替代 nil
,使其有具体含义。
在 1.21 之前,
panic(nil)
和没有发生恐慌,recover
都返回nil
,无法区分这两种状态。
func nopanic() { defer func() { e := recover() println(e == nil) // true }() } func panicnil() { defer func() { e := recover() println(e == nil) // false _, ok := e.(*runtime.PanicNilError) println(ok) // true }() panic(nil) }
适用场景:
- 在调用堆栈任意位置中断,跳转到合适位置(恢复,参数为依据)。
runtime.Goexit
:不能用于 main goroutine,进程崩溃。os.Exit
:进程立即终止,延迟调用不会执行。
- 无法恢复的故障,输出调用堆栈现场。
- 文件系统损坏。
- 数据库无法连接。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论