当前位置: 首页 > news >正文

【Golang笔记03】error、panic、fatal错误处理学习笔记

Golang笔记:错误处理学习笔记

一、进阶学习

1.1、错误(异常处理)

Go语言中也有和Java中的异常处理相关的机制,不过,在Go里面不叫异常,而是叫做:错误。错误分为三类,分别是:

  • error:部分流程错误,需要程序员进行处理,这种级别的错误,不会导致整个程序停止运行。
  • panic:严重错误,等程序执行完成之后,会立即退出运行。
  • fatal:致命错误,整个程序立即停止运行。

Go语言中,不存在try..catch相关的语句,Go中的错误是通过返回值的形式返回的,例如:在调用一个方法的时候,这个方法可以增加一个返回值,表示是否存在错误信息,然后通过if条件语句判断,是否能够正常执行后续代码。

上面这种错误的处理方式,虽然简化了try...catch的使用,但是,引入了if条件语句,也会导致代码里面出现一大堆的条件判断,所以,从本质上来说,还是没有简化的,只不过是采用另一种方式处理错误而已。

1.1.1、error错误

Go中有一个error接口,接口里面只有一个Error()方法,返回值是一个string字符串,这个字符串表示错误信息。Go中创建error错误,可以通过下面两种方式:

  • 第一种方式:使用errors包下的New()函数。
  • 第二种方式:使用fmt包下的Errorf()函数。
package mainimport ("errors""fmt"
)// 返回值第二个参数是 error 错误
func div(a int, b int) (int, error) {if b == 0 {// 定义 error 级别的错误返回return -1, errors.New("分母不能为0")// 或者使用下面这种方式// return -1, fmt.Errorf("分母不能为0")}c := a / breturn c, nil
}func main() {i, err := div(1, 0)if err != nil {fmt.Println(err)return}fmt.Println("结果=", i)
}
1.1.2、自定义error错误

Go中也允许程序开发人员自定义error级别的错误。自定义错误很简单,只需要对应的类型,实现了Error()方法,那么这个类型就将被看作是error接口的实现类,也就属于error类型的错误了。

自定义error错误的步骤,如下所示:

  • 第一步:自定义一个类型。
  • 第二步:实现Error()方法,返回string错误字符串信息。
  • 第三步:创建自定义类型对象,作为error错误即可。

案例代码,如下所示:

package mainimport ("fmt"
)// 第一步:MyCustomError 自定义错误类型
type MyCustomError struct {code intmsg  string
}// 第二步:实现 error 接口的 Error() 方法
func (e MyCustomError) Error() string {// 自定义错误的返回格式return fmt.Sprintf("自定义错误:code=%d, msg=%s", e.code, e.msg)
}func main() {// 第三步:创建自定义错误customError := MyCustomError{code: 1,msg:  "分母不能为0",}fmt.Println(customError)
}
1.1.3、Unwrap解包错误

Go语言中,允许错误嵌套,一个错误嵌套一个错误,从而形成一个错误链表。要想从一个错误中,获取它包含的错误,可以调用errors中提供的Unwrap()方法。

  • Unwrap()方法:从当前error错误中,获取其内部包含的error错误,如果内部不存在error错误,则返回nil;如果存在,则返回内部的error错误。

需要注意的是,Unwrap()方法获取到的error错误,有可能也是嵌套的error错误,如果想要获取指定的error错误,可以使用递归的方式进行判断。

package mainimport ("errors""fmt"
)// MyCustomError 自定义错误类型
type MyCustomError struct {code intmsg  string
}// 实现 error 接口的 Error() 方法
func (e MyCustomError) Error() string {// 自定义错误的返回格式return fmt.Sprintf("自定义错误:code=%d, msg=%s", e.code, e.msg)
}func main() {// 创建自定义错误customError := MyCustomError{code: 1,msg:  "分母不能为0",}// 嵌套错误,形成一个错误链err := fmt.Errorf("错误:%w", customError)fmt.Println(err)// 解包错误err = errors.Unwrap(err)fmt.Println(err)// 解包错误err = errors.Unwrap(err)fmt.Println(err)
}
// 执行结果
错误:自定义错误:code=1, msg=分母不能为0
自定义错误:code=1, msg=分母不能为0
<nil>

从上面可以看到,errors.Unwrap()方法的作用就是从错误链中解包内部错误。

1.1.4、检查错误

Go语言中,判断某个error错误是否为指定类型,可以使用errors.Is()方法,这个方法的作用是:判断当前error是否为目标target类型的错误,如果是目标类型的错误,则返回true,否则返回false

  • errors.Is(err, targer error) bool方法:判断是否为目标错误类型。
package mainimport ("errors""fmt"
)var originalErr = errors.New("this is an error")// 包裹原始错误
func wrap1() error {return fmt.Errorf("wrapp error %w", wrap2())
}// 原始错误
func wrap2() error {return originalErr
}func main() {err := wrap1()// 如果使用if err == originalErr 将会是falseif errors.Is(err, originalErr) {fmt.Println("original")}
}

另外,Go中还提供了一个errors.As(err, target Any) bool方法,这个方法的作用是:在当前错误链中寻找第一个匹配target的错误类型,并且将找到的错误赋值给传入的err变量。

  • errors.As(err, target Any) bool方法:用于检查一个错误是否可以转换成目标,如果可以就会将目标错误赋值给第一个参数err
package mainimport ("errors""fmt""time"
)// TimeError 自定义error
type TimeError struct {Msg string// 记录发生错误的时间Time time.Time
}// 实现 error 接口方法
func (m TimeError) Error() string {return m.Msg
}// NewMyError 定义自定义错误函数,并且使用 & 符号,返回一个 结构体指针 类型
func NewMyError(msg string) error {// 使用 & 符号,返回一个 结构体指针 类型,即:返回内存地址return &TimeError{Msg:  msg,Time: time.Now(),}
}// 包裹原始错误
func wrap1() error {return fmt.Errorf("wrapp error %w", wrap2())
}// 原始错误
func wrap2() error {return NewMyError("original error")
}func main() {// 定义一个 结构体指针 变量var myerr *TimeError// 获取错误err := wrap1()// 检查错误链中是否有 *TimeError 类型的错误if errors.As(err, &myerr) { // 输出TimeError的时间fmt.Println("original", myerr.Time)}
}

1.2、panic

前面学的error错误,是不严重的错误类型,如果不在后面写return语句,那么程序还是会继续往后执行下去的。Go语言中还提供了一个panic错误。

panic类型是一种比error更加严重的错误类型,当出现panic之后,后续的代码时不会继续执行,即使没有使用return关键字,代码也不会执行panic后面的代码。

注意:panic你可以理解成是Java语言中的使用throw new抛出异常的模式。

1.2.1、创建panic

Go中提供了一个panic函数,通过这个函数可以创建panic错误。

func panic(v any)

我对panic的理解是,panic就相当于是简写了Java语言中的throw new Exception("错误信息")的语句。例如:

// 在Java语言中,抛出异常
throw new Exception("错误异常信息")// 而在Go里面,只需要写一个 panic 函数即可
panic("错误异常信息")

使用panic的时候,需要注意的是,panic后面的代码是不会执行的。

1.2.2、panic善后

当发生panic错误之前,如果存在defer延迟函数,那么程序首先会依次执行defer延迟函数,执行完成之后,才会触发panic

package mainimport "fmt"func mockPanic() {fmt.Println("开始执行panic...")panic("模拟panic错误...")fmt.Println("panic执行结束...")
}
func demo01() {fmt.Println("执行demo01()函数...")
}
func demo02() {fmt.Println("执行demo02()函数...")
}
func demo03() {fmt.Println("执行demo03()函数...")
}func main() {fmt.Println("Hello")defer demo01()defer demo02()mockPanic()defer demo03()fmt.Println("World")
}// 程序运行结果
Hello
开始执行panic...
执行demo02()函数...
执行demo01()函数...
panic: 模拟panic错误...goroutine 1 [running]:
main.mockPanic()D:/environment/GoWorks/src/go-study/Hello.go:7 +0x59
main.main()D:/environment/GoWorks/src/go-study/Hello.go:24 +0x7d

当发生panic时,会立即停止当前函数的执行,然后执行善后的一些操作,例如:执行defer延迟函数;执行完成之后,会将panic向上层函数传递,上层函数也会进行相应的善后操作,直到main函数终止运行。

1.2.3、恢复执行

当使用panic之后,程序会终止运行,如果我们想恢复程序的执行,那么可以使用recover()内置函数来实现。需要注意的是,recover()函数必须在defer延迟函数里面使用。另外,recover()函数的返回值是panic函数中的错误信息内容。

package mainimport ("fmt"
)func demo() {fmt.Println("C")defer func() {fmt.Println("D")// 恢复程序执行,就相当于是取消panicok := recover()if ok != nil {fmt.Println("恢复程序执行")}fmt.Println("E")}()// 发生 panic 则 demo 方法终止执行panic("模拟panic错误...")fmt.Println("F")
}func main() {fmt.Println("A")demo()fmt.Println("B")
}// 执行结果
A
C
D
恢复程序执行
E
B

从上面代码中,可以发现,正常情况下,发生panic之后,如果没有使用recover()函数恢复执行,那么最终的输出结果将只有下面这些内容:

A
C
D
E
panic: 模拟panic错误...goroutine 1 [running]:
main.demo()D:/environment/GoWorks/src/go-study/Hello.go:19 +0x76
main.main()D:/environment/GoWorks/src/go-study/Hello.go:25 +0x4f

但是,我们在demo()函数中,定义了一个defer延迟函数,并且在里面使用recover()函数恢复程序的执行,也就相当于是捕获处理了panic,那么demo()函数中的panic就不会向上传递到main函数里面,所以main函数中的代码将正常执行。

理解这一点很重要,因为如果把上面代码改成下面执行顺序,如下所示:

package mainimport ("fmt"
)func demo() {fmt.Println("C")// 发生 panic 则 demo 方法终止执行panic("模拟panic错误...")fmt.Println("F")
}func main() {fmt.Println("A")// 在main函数里面定义deferdefer func() {fmt.Println("D")// 恢复程序执行,就相当于是取消panicok := recover()if ok != nil {fmt.Println("恢复程序执行")}fmt.Println("E")}()demo()fmt.Println("B")
}// 执行结果
A
C
D
恢复程序执行
E

上面代码中,从输出结果来看,程序没有执行demo()函数之后的代码,这是为什么呢???

  • 我是这么理解的,当demo()函数中,发生panic之后,由于demo()函数没有进行处理,所以会向上传递到main()函数里面。
  • main()函数发现,此时发生了panic,所以就不会执行后面的代码。
  • 此时触发panic的善后相关代码,例如:执行defer函数。
  • 首先,会从demo()函数中,依次开始执行defer函数,然后向上传递到main函数里面,开始执行main函数中的defer函数。
  • defer函数执行完成之后,此时整个程序就结束运行了。
  • 所以,最终demo()函数之后的代码,也就不会再执行了。

下面举个例子,来看看发生panic之后,程序的输出结果分别是多少,如下所示:

package mainimport ("fmt"
)func demo() {fmt.Println("B1")defer fmt.Println("C")defer func() {fmt.Println("E1")// 恢复程序执行,就相当于是取消panicok := recover()if ok != nil {fmt.Println("恢复程序执行")}fmt.Println("E2")}()demo02()fmt.Println("B2")
}func demo02() {fmt.Println("D1")panic("demo02函数发生panic...")fmt.Println("D2")
}func main() {fmt.Println("A1")demo()fmt.Println("A2")
}// 输出结果
// A1 B1 D1 E1 恢复... E2 C A2

看到这里,你学会了吗???

使用recover()函数,有四个注意事项:

  • 必须在defer函数中使用recover()函数。
  • 多次使用recover()函数,只会恢复一个panic
  • 闭包结构中使用recover()函数,不能恢复外部函数的panic
  • panic参数禁止使用nil

可以将recover()函数理解成是Java中的try...catch的功能,也就是捕获panic错误,然后能够让程序继续正常执行。

1.3、fatal

fatalGo语言中的致命错误,这种错误一旦发生,那么整个程序就会立即停止运行,fatal是没有办法进行善后操作的,也就是说,发生fatal之后,程序都来不及执行defer函数。

package mainimport ("fmt""os"
)func main() {fmt.Println("A1")// 模拟 fatal 错误os.Exit(1)fmt.Println("A2")// A1 B1 D1 E1 恢复 E2 C A2
}

Go语言中一般不会显示的声明fatal错误,发生fatal错误一般都是程序自主发生的,不是人为干预的。

注意:Go语言中一般使用os.Exit(1)代码来实现fatal错误。

以上,就是Go语言中错误处理相关的知识点。

相关文章:

  • Go语言中内存释放 ≠ 资源释放
  • Java详解LeetCode 热题 100(20):LeetCode 48. 旋转图像(Rotate Image)详解
  • Linux入门(九)任务调度
  • 【Go】1、Go语言基础
  • 【Java高阶面经:消息队列篇】23、Kafka延迟消息:实现高并发场景下的延迟任务处理
  • 今日行情明日机会——20250523
  • Selenium 测试框架 - Java
  • el-input宽度自适应方法总结
  • 深入解析Spring Boot与Redis集成:高性能缓存实践
  • [crxjs]自己创建一个浏览器插件
  • Android中Binder驱动作用?
  • 【AS32X601驱动系列教程】GPIO_点亮LED详解
  • 服务器修改/home的挂载路径
  • HTB-Season8-Puppy-WriteUp
  • Teensy LC 一款由 PJRC 公司开发的高性能 32 位微控制器开发板
  • 图解深度学习 - 机器学习简史
  • 【Mini-F5265-OB开发板试用测评】2、关于platform.c中的串口号初始化修改的建议
  • vue中v-clock指令
  • 分布式消息队列kafka详解
  • Vue3.5 企业级管理系统实战(二十):角色菜单
  • h5网站开发多少钱/南昌seo搜索优化
  • 网站建设现在好做吗/世界杯最新排名
  • 长沙专业网站设计平台/中国品牌策划公司排名
  • 做的网站很卡是什么原因/为什么不建议去外包公司上班
  • 网站建设玖金手指排名13/网站信息
  • 建建建设网站公司网站/自媒体运营主要做什么