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) }
  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 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)
}
}
  1. 访问数据库时我们返回了原始的错误信息
  2. 到上层我们添加了一些自定义的上下文信息
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)
}
}
  1. 首先我们自定义了ForbiddenError的错误类型
  2. 我们实现了error接口
  3. 访问数据库抛出原始错误
  4. 上层返回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)
}
}
  1. 使用%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 {
// ...
}
}

golang中的错误处理的更多相关文章

  1. Golang中的panic和recover(捕获异常)

    func panic(interface{})和func recover() interface{}是Golang中用于错误处理的两个函数. panic的作用就是抛出一条错误信息,从它的参数类型可以看 ...

  2. [Golang]一些书城项目中出现错误的原因和解决办法(一)

    跟着B站尚硅谷的GoWeb教程写书城项目,整理一下自己写的时候出现的错误和解决办法. 错误一:cartItem中只能加入一种书,SQL语句没有问题,但是购物车中的总金额和总数量正确: 原因:cartI ...

  3. Golang 中的坑 一

    Golang 中的坑 短变量声明  Short variable declarations 考虑如下代码: package main import ( "errors" " ...

  4. golang中Context的使用场景

    golang中Context的使用场景 context在Go1.7之后就进入标准库中了.它主要的用处如果用一句话来说,是在于控制goroutine的生命周期.当一个计算任务被goroutine承接了之 ...

  5. Golang中的自动伸缩和自防御设计

    Raygun服务由许多活动组件构成,每个组件用于特定的任务.其中一个模块是用Golang编写的,负责对iOS崩溃报告进行处理.简而言之,它接受本机iOS崩溃报告,查找相关的dSYM文件,并生成开发者可 ...

  6. golang中发送http请求的几种常见情况

    整理一下golang中各种http的发送方式 方式一 使用http.Newrequest 先生成http.client -> 再生成 http.request -> 之后提交请求:clie ...

  7. 【荐】详解 golang 中的 interface 和 nil

    golang 的 nil 在概念上和其它语言的 null.None.nil.NULL一样,都指代零值或空值.nil 是预先说明的标识符,也即通常意义上的关键字.在 golang 中,nil 只能赋值给 ...

  8. [golang 易犯错误] golang 局部变量初始化:=的陷阱

    我们知道,golang中局部变量初始化方法(使用“:=”创建并赋值),让我们在使用变量时很方便.但是,这也是易犯错误的地方之一.特别是这个初始化符还支持多个变量同时初始化,更特别的是它还支持原有变量赋 ...

  9. [转]Golang 中使用 JSON 的小技巧

    taowen是json-iterator的作者. 序列化和反序列化需要处理JSON和struct的关系,其中会用到一些技巧. 原文 Golang 中使用 JSON 的小技巧是他的经验之谈,介绍了一些s ...

随机推荐

  1. Springboot 通过FastJson实现bean对象和Json字符串互转

    Json格式在后台服务中的重要性就不多说了,直入正题.首先引入pom文件,这里使用的是1.2.83版本 1 <dependency> 2 <groupId>com.alibab ...

  2. 如何在 HTML 中调整图像大小?

    了解在 HTML 中调整图像大小的不同技术.何时应避免在浏览器端调整大小,以及在 Web 上操作和提供图像的正确方法. 如果您的图像不适合布局,您可以在 HTML 中调整其大小.在 HTML 中调整图 ...

  3. 动态规划——leetcode55、跳跃游戏

    1.题目描述:  2.解题方法:动态规划 动态规划解题步骤: 1.确定状态: 最后一步:如果能跳到最后一个下标,我们考虑他的最后一步到n-1(最后一个下标),这一步是从 i 跳过来的,i<n-1 ...

  4. 【java】学习路线4-对象、嵌套引用、匿名对象

    class Learn03_MyClass{    String name = "www.pornhub.com";//成员变量:属性    public void Hello() ...

  5. C++ IO流_数据的旅行之路

    1. 前言 程序中的数据总是在流动着,既然是流动就会有方向.数据从程序的外部流到程序内部,称为输入:数据从程序内部流到外部称为输出. C++提供有相应的API实现程序和外部数据之间的交互,统称这类AP ...

  6. 【读书笔记】C#高级编程 第十一章 LINQ

    (一)LINQ概述 语言集成查询(Language Integrated Query,LINQ)在C#编程语言中继承了查询语法,可以用相同的语法访问不同的数据源. 1.LINQ查询 var query ...

  7. FreeSql 导入数据的各种场景总结 [C#.NET ORM]

    前言 导入数据这种脏活.累活,相信大家多多少少都有经历,常见的场景有: 同服务器从A表导数据到B表 批量导入新数据 批量新增或更新数据 跨服务器从A表导数据到B表 每种场景有自己的特点,我们一般会根据 ...

  8. C#/VB.NET 如何在Excel中使用条件格式设置交替行颜色

    说起高亮数据行,不让人想起了交替颜色行,有的人把交替颜色行也都设置成高亮,不仅不美观,而且对阅读还是个干扰.隔行交替的颜色是为了阅读不串行,这些行只是环境,数据才是主体.那么如何通过C#/VB.NET ...

  9. Web 开发相关概念

    Web 开发相关概念 前端 后端 全栈 前端 Front-end:网站中用户看到的部分 HTML CSS JavaScript 库和框架 设计工具 后端 Back-end:网站中用户看不到的部分 编程 ...

  10. 如何使用 Git 管理配置文件

    现在很多软件的配置都可以在线同步或者支持导入导出,可以很方便的在不同设备上使用.但电脑上还有很多本地配置文件没有办法同步,夸多个设备使用时很难保持一致,换电脑也很麻烦.其实可以使用 Git 来管理这些 ...