0.1、索引
https://waterflow.link/articles/1666716727236
1、panic
当咱们执行 panic 的时候会完结上面的流程:
package main
import "fmt"
func main() {fmt.Println("hello")
panic("stop")
fmt.Println("world")
}
go run 9.go
hello
panic: stop
然而 panic 也是能够捕捉的,咱们能够应用 defer 和 recover 实现:
package main
import "fmt"
func main() {defer func() {if r := recover(); r != nil {fmt.Println("recover:", r)
}
}()
fmt.Println("hello")
panic("stop")
fmt.Println("world")
}
go run 9.go
hello
recover: stop
那什么时候适宜 panic 呢?在 Go 中,panic 用于示意真正的异样,例如程序谬误。咱们常常会在一些内置包外面看到 panic 的身影。
比方 strings.Repeat 反复返回一个由字符串 s 的计数正本组成的新字符串:
func Repeat(s string, count int) string {
if count == 0 {return ""}
//
if count < 0 {panic("strings: negative Repeat count")
} else if len(s)*count/count != len(s) {panic("strings: Repeat count causes overflow")
}
...
}
咱们能够看到当反复的次数小于 0 或者反复 count 次之后 s 的长度溢出,程序会间接 panic,而不是返回谬误。这时因为 strings 包限度了 error 的应用,所以在程序谬误时会间接 panic。
还有一个例子是对于正则表达式的例子:
package main
import (
"fmt"
"regexp"
)
func main() {pattern := "a[a-z]b*" // 1
compile, err := regexp.Compile(pattern) // 2
if err != nil { // 2
fmt.Println("compile err:", err)
return
}
// 3
allString := compile.FindAllString("acbcdadb", 3)
fmt.Println(allString)
}
- 编写一个正则表达式
- 调用 Compile,解析正则表达式,如果胜利,返回用于匹配文本的 Regexp 对象。否则返回谬误
- 利用正则,在输出的字符串中,获取所有的匹配字符
能够看到如果下面正则解析失败是能够持续往下执行的,然而 regexp 包中还有另外一个办法 MustCompile:
func MustCompile(str string) *Regexp {regexp, err := Compile(str)
if err != nil {panic(`regexp: Compile(` + quote(str) + `): ` + err.Error())
}
return regexp
}
这个办法阐明正则的解析是强依赖的,如果解析谬误,间接 panic 完结程序。用户能够依据理论状况抉择。
然而理论开发中咱们还是要审慎应用 panic,因为它会使程序完结运行(除非咱们调用 defer recover)
2、包装谬误
谬误包装是将谬误包装或者打包在一个包装容器中,这样的话咱们就能够追溯到源谬误。谬误包装的次要作用就是:
- 为谬误增加上下文
- 将谬误标记为特定类型的谬误
咱们能够看一个拜访数据库的例子:
package main
import (
"fmt"
"github.com/pkg/errors"
)
type Courseware struct {
Id int64
Code string
Name string
}
func getCourseware(id int64) (*Courseware, error) {courseware, err := getFromDB(id)
if err != nil {return nil, errors.Wrap(err, "六月的想拜访这个课件") // 2
}
return courseware, nil
}
func getFromDB(id int64) (*Courseware, error) {return nil, errors.New("permission denied") // 1
}
func main() {_, err := getCourseware(11)
if err != nil {fmt.Println(err)
}
}
- 拜访数据库时咱们返回了原始的错误信息
- 到下层咱们增加了一些自定义的上下文信息
go run 9.go
六月的想拜访这个课件: permission denied
当然咱们也能够将谬误包装成咱们自定义类型的谬误,咱们略微批改下下面的例子:
package main
import (
"fmt"
"github.com/pkg/errors"
)
type Courseware struct {
Id int64
Code string
Name string
}
// 1
type ForbiddenError struct {Err error}
// 2
func (e *ForbiddenError) Error() string {return "Forbidden:" + e.Err.Error()
}
func getCourseware(id int64) (*Courseware, error) {courseware, err := getFromDB(id)
if err != nil {return nil, &ForbiddenError{err} // 4
}
return courseware, nil
}
func getFromDB(id int64) (*Courseware, error) {return nil, errors.New("permission denied") // 3
}
func main() {_, err := getCourseware(11)
if err != nil {fmt.Println(err)
}
}
- 首先咱们自定义了 ForbiddenError 的谬误类型
- 咱们实现了 error 接口
- 拜访数据库抛出原始谬误
- 下层返回 ForbiddenError 类型的谬误
go run 9.go
Forbidden: permission denied
当然咱们也能够不必创立自定义谬误的类型,去包装谬误增加上下文:
package main
import (
"fmt"
"github.com/pkg/errors"
)
type Courseware struct {
Id int64
Code string
Name string
}
func getCourseware(id int64) (*Courseware, error) {courseware, err := getFromDB(id)
if err != nil {return nil, fmt.Errorf("another wrap err: %w", err) // 1
}
return courseware, nil
}
func getFromDB(id int64) (*Courseware, error) {return nil, errors.New("permission denied")
}
func main() {_, err := getCourseware(11)
if err != nil {fmt.Println(err)
}
}
- 应用 %w 包装谬误
应用这的益处是咱们能够追溯到源谬误,从而不便咱们做一些非凡的解决。
还有一种形式是应用:
return nil, fmt.Errorf("another wrap err: %v", err)
%v 的形式不会包装谬误,所以无奈追溯到源谬误,但往往有时候咱们会抉择这种形式,而不必 %w 的形式。%w 的形式尽管能包装源谬误,但往往咱们会通过源谬误去做一些解决,如果源谬误被批改,那包装这个源谬误的相干谬误都须要做响应变动。
3、谬误类型判断
咱们扩大一下下面查问课件的例子。当初咱们有这样的判断,如果传进来的 id 不非法咱们返回 400 谬误,如果查询数据库报错咱们返回 500 谬误,咱们能够像上面这样写:
package main
import (
"fmt"
"github.com/pkg/errors"
)
type Courseware struct {
Id int64
Code string
Name string
}
type ForbiddenError struct {Err error}
func (e *ForbiddenError) Error() string {return "Forbidden:" + e.Err.Error()
}
func getCourseware(id int64) (*Courseware, error) {
if id <= 0 {return nil, fmt.Errorf("invalid id: %d", id)
}
courseware, err := getFromDB(id)
if err != nil {return nil, &ForbiddenError{err}
}
return courseware, nil
}
func getFromDB(id int64) (*Courseware, error) {return nil, errors.New("permission denied")
}
func main() {_, err := getCourseware(500) // 咱们能够批改这里的 id 看下打印的构造
if err != nil {switch err := err.(type) {
case *ForbiddenError:
fmt.Println("500 err:", err)
default:
fmt.Println("400 err:", err)
}
}
}
go run 9.go
500 err: Forbidden: permission denied
这样看起来如同也没什么问题,当初咱们略微批改下代码,把下面 ForbiddenError 包装一下:
package main
import (
"fmt"
"github.com/pkg/errors"
)
type Courseware struct {
Id int64
Code string
Name string
}
type ForbiddenError struct {Err error}
func (e *ForbiddenError) Error() string {return "Forbidden:" + e.Err.Error()
}
func getCourseware(id int64) (*Courseware, error) {
if id <= 0 {return nil, fmt.Errorf("invalid id: %d", id)
}
courseware, err := getFromDB(id)
if err != nil {return nil, fmt.Errorf("wrap err: %w", &ForbiddenError{err}) // 这里包装了一层谬误
}
return courseware, nil
}
func getFromDB(id int64) (*Courseware, error) {return nil, errors.New("permission denied")
}
func main() {_, err := getCourseware(500)
if err != nil {switch err := err.(type) {
case *ForbiddenError:
fmt.Println("500 err:", err)
default:
fmt.Println("400 err:", err)
}
}
}
go run 9.go
400 err: wrap err: Forbidden: permission denied
能够看到咱们的 Forbidden 谬误进到了 400 外面,这并不是咱们想要的后果。之所以会这样,是因为在 ForbiddenError 的里面又包装了一层 Error 谬误,应用类型断言的时候判断进去的是 Error 谬误,所以进到了 400 分支。
这里咱们能够应用 errors.As 办法,它会递归调用 Unwrap 办法,找到谬误链中第一个与 target 匹配的办法:
package main
import (
"fmt"
"github.com/pkg/errors"
)
type Courseware struct {
Id int64
Code string
Name string
}
type ForbiddenError struct {Err error}
func (e *ForbiddenError) Error() string {return "Forbidden:" + e.Err.Error()
}
func getCourseware(id int64) (*Courseware, error) {
if id <= 0 {return nil, fmt.Errorf("invalid id: %d", id)
}
courseware, err := getFromDB(id)
if err != nil {return nil, fmt.Errorf("wrap err: %w", &ForbiddenError{err})
}
return courseware, nil
}
func getFromDB(id int64) (*Courseware, error) {return nil, errors.New("permission denied")
}
func main() {_, err := getCourseware(500)
if err != nil {
var f *ForbiddenError // 这里实现了 *ForbiddenError 接口,不然会 panic
if errors.As(err, &f) { // 找到匹配的谬误
fmt.Println("500 err:", err)
} else {fmt.Println("400 err:", err)
}
}
}
go run 9.go
500 err: wrap err: Forbidden: permission denied
4、谬误值判断
在代码中或者 mysql 库或者 io 库中咱们常常会看到这样的全局谬误:
var ErrCourseware = errors.New("courseware")
这种谬误咱们称之为哨兵谬误。个别数据库没查到 ErrNoRows 或者 io 读到了 EOF 谬误,这些特定的谬误能够帮忙咱们做一些非凡的解决。
个别咱们会间接用 == 号判断谬误值,然而就像下面的如果谬误被包装哪咱们就不好去判断了。好在 errors 包中提供了 errors.Is 办法,通过递归调用 Unwrap 判断谬误链中是否与指标谬误相匹配的谬误值:
if err != nil {if errors.Is(err, ErrCourseware) {// ...} else {// ...}
}