golang中的错误处理

0.1、索引https://waterflow.link/articles/1666716727236
1、panic当我们执行panic的时候会结束下面的流程:
package mainimport "fmt"func main() { fmt.Println("hello") panic("stop") fmt.Println("world")}go run 9.gohellopanic: stop但是panic也是可以捕获的 , 我们可以使用defer和recover实现:
package mainimport "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.gohellorecover: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 mainimport ( "fmt" "regexp")func main() { pattern := "a[a-z]b*" // 1 compile, err := regexp.Compile(pattern) // 2 if err != nil { // 2fmt.Println("compile err: ", err)return }// 3 allString := compile.FindAllString("acbcdadb", 3) fmt.Println(allString)}

  1. 编写一个正则表达式
  2. 调用Compile,解析正则表达式,如果成功 , 返回用于匹配文本的 Regexp 对象 。否则返回错误
  3. 利用正则,在输入的字符串中,获取所有的匹配字符
可以看到如果上面正则解析失败是可以继续往下执行的,但是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、包装错误错误包装是将错误包装或者打包在一个包装容器中 , 这样的话我们就可以追溯到源错误 。错误包装的主要作用就是:
  1. 为错误添加上下文
  2. 将错误标记为特定类型的错误
我们可以看一个访问数据库的例子:
package mainimport ( "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) }}
  1. 访问数据库时我们返回了原始的错误信息
  2. 到上层我们添加了一些自定义的上下文信息
go run 9.go六月的想访问这个课件: permission denied当然我们也可以将错误包装成我们自定义类型的错误,我们稍微修改下上面的例子:
package mainimport ( "fmt" "github.com/pkg/errors")type Courseware struct { Id int64 Code string Name string}// 1type ForbiddenError struct { Err error}// 2func (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) }}
  1. 首先我们自定义了ForbiddenError的错误类型
  2. 我们实现了error接口
  3. 访问数据库抛出原始错误
  4. 上层返回ForbiddenError类型的错误
go run 9.goForbidden: permission denied当然我们也可以不用创建自定义错误的类型,去包装错误添加上下文:
package mainimport ( "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) }}

推荐阅读