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 {
// ...
}
}
发表回复