李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
GO
正文
30.Golang之错误和异常处理
Leefs
2022-07-25 PM
1218℃
0条
[TOC] ### 前言 Go语言没有类似 Java 或 .NET 中的异常处理机制,虽然可以使用 defer、panic、recover 模拟,但官方并不主张这样做,Go语言的设计者认为其他语言的异常机制已被过度使用,上层逻辑需要为函数发生的异常付出太多的资源,同时,如果函数使用者觉得错误处理很麻烦而忽略错误,那么程序将在不可预知的时刻崩溃。 Go语言希望开发者将错误处理视为正常开发必须实现的环节,正确地处理每一个可能发生错误的函数,同时,Go语言使用返回值返回错误的机制,也能大幅降低编译器、运行时处理错误的复杂度,让开发者真正地掌握错误的处理。 ![30.Golang之错误和异常处理01.png](https://lilinchao.com/usr/uploads/2022/07/3209813751.png) ### 一、错误和异常 + **错误指的是可能出现问题的地方出现了问题。** 比如打开一个文件时失败,这种情况在人们的意料之中 。 + **异常指的是不应该出现问题的地方出现了问题。** 比如引用了空指针,这种情况在人们的意料之外。 错误是业务过程的一部分,而异常不是 。 Go 中的错误也是一种类型。错误用内置的 `error` 类型表示。就像其他类型的,如 int,float64。错误值可以存储在变量中,从函数中返回,等等。 **触发错误示例** 因为这里没有存在一个文件 `go.txt` ,所以尝试打开文件将会返回一个不等于 `nil` 的错误。 ```go package main import ( "fmt" "os" ) func main() { //尝试打开文件 file, err := os.Open("/go.txt") // 如果打开文件时发生错误 返回一个不等于 nil 的错误 if err != nil { fmt.Println(err) return } // 如果打开文件成功,返回一个文件句柄和一个值为nil的错误 fmt.Println(file.Name(), "success") } ``` **运行结果** ``` open /go.txt: The system cannot find the file specified. ``` 如果一个函数或方法返回一个错误,那么按照惯例,它必须是函数返回的最后一个值。因此,`Open` 函数返回的值是最后一个值。 处理错误的惯用方法是将返回的错误与nil进行比较。nil 值表示没有发生错误,而非 nil 值表示出现错误。在我们的例子中,我们检查错误是否为 nil。如果它不是 nil,我们只需打印错误并从主函数返回。 ### 二、错误类型 #### 2.1 错误类型 Go 语言通过内置的错误接口提供了非常简单的错误处理机制。 在 Go 语言中,错误是一个带有以下定义的接口类型: ```go type error interface { Error() string } ``` 它包含一个带有 `Error()` 字符串的方法。任何实现这个接口的类型都可以作为一个错误使用。这个方法提供了对错误的描述。 当打印错误时,`fmt.Println()` 函数在内部调用 Error() 方法来获取错误的描述。这就是错误描述是如何在一行中打印出来的。 #### 2.2 自定义错误 返回错误前,需要定义会产生哪些可能的错误,在Go语言中,使用 errors 包进行错误的定义。 **格式如下:** ```go var err = errors.New("this is an error") ``` 错误字符串由于相对固定,一般在包作用域声明,应尽量减少在使用时直接使用 errors.New 返回。 + **errors 包** Go语言的 errors 中对 New 的定义非常简单,代码如下: ```go // 创建错误对象 func New(text string) error { //将 errorString 结构体实例化,并赋值错误描述的成员 return &errorString{text} } // 错误字符串 type errorString struct { //声明 errorString 结构体,拥有一个成员,描述错误内容 s string } // 返回发生何种错误 func (e *errorString) Error() string { //实现 error 接口的 Error() 方法,该方法返回成员中的错误描述 return e.s } ``` `errorString` 是一个结构体类型,只有一个字符串字段 `s` 。它使用了 `errorString` 指针接收者,来实现 `error` 接口的 `Error() string` 方法。 `New` 函数有一个字符串参数,通过这个参数创建了 `errorString` 类型的变量,并返回了它的地址。于是它就创建并返回了一个新的错误。 ##### 在代码中使用错误定义 > 创建了一个计算矩形面积的函数,当矩形的长和宽两者有一个为负数时,就会返回一个错误 ```go package main import ( "errors" "fmt" ) func area(x, y int) (int, error) { if x <= 0 || y <= 0 { //通过errors.New,创建错误对象 return 0, errors.New("参数错误,长度或宽度,不能小于等于0.") } return x * y, nil } func main() { x := 10 //y := 10 y := -10 r, err := area(x, y) //当错误不为nil时,打印输出定义的错误信息 if err != nil { fmt.Println(err) return } fmt.Println("Area =", r) } ``` **运行结果** ``` 参数错误,长度或宽度,不能小于等于0. ``` ##### 在解析中使用自定义错误 使用 `errors.New` 定义的错误字符串的错误类型是无法提供丰富的错误信息的,那么,如果需要携带错误信息返回,就需要借助**自定义结构体实现错误接口**。 ```go package main import ( "fmt" ) type areaError struct { // 错误信息 err string // 长度 length int // 宽度 width int } // 使用指针接收者 *areaError 实现了 error 接口的 Error() string 方法 func (e *areaError) Error() string { return e.err } // 长度为负数返回 true func (e *areaError) lengthNegative() bool { return e.length < 0 } // 宽度为负数返回 true func (e *areaError) widthNegative() bool { return e.width < 0 } func area(length, width int) (int, error) { err := "" if length < 0 { err += "length is less than zero" } if width < 0 { if err == "" { err = "width is less than zero" } else { err += " and width is less than zero" } } if err != "" { return 0, &areaError{err, length, width} } return length * width, nil } func main() { length := 10 width := -10 area, err := area(length, width) // 检查了错误是否为 nil if err != nil { // 断言 *areaError 类型 if err, ok := err.(*areaError); ok { // 如果错误是 *areaError 类型 // 如果长度为负数 打印错误长度具体值 if err.lengthNegative() { fmt.Printf("error: 长度 %d 小于0\n", err.length) } // 如果宽度为负数 打印错误宽度具体值 if err.widthNegative() { fmt.Printf("error: 宽度 %d 小于0\n", err.width) } return } fmt.Println(err) return } fmt.Println("Area =", area) } ``` **运行结果** ``` error: 宽度 -10 小于0 ``` 相对于之前例子,这次报错结果更加具体。 ### 三、异常 错误和异常是两个不同的概念,非常容易混淆。 **错误指的是可能出现问题的地方出现了问题;而异常指的是不应该出现问题的地方出现了问题。** #### 3.1 panic 在有些情况,当程序发生异常时,无法继续运行。在这种情况下,我们会使用 `panic` 来终止程序。当函数发生 `panic` 时,它会终止运行,在执行完所有的延迟函数后,程序返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 `panic` 信息,接着打印出堆栈跟踪,最后程序终止。 我们应该尽可能地使用错误,而不是使用 `panic` 和 `recover` 。只有当程序不能继续运行的时候,才应该使用 `panic` 和 `recover` 机制。 **`panic` 有两个合理的用例:** - 发生了一个不能恢复的错误,此时程序不能继续运行。一个例子就是 web 服务器无法绑定所要求的端口。在这种情况下,就应该使用 `panic` ,因为如果不能绑定端口,啥也做不了。 - 发生了一个编程上的错误。假如我们有一个接收指针参数的方法,而其他人使用 `nil` 作为参数调用了它。在这种情况下,我们可以使用 `panic` ,因为这是一个编程错误:用 `nil` 参数调用了一个只能接收合法指针的方法。 #### 3.2 触发 panic 下面是内建函数 `panic` 的签名: ```go func panic(v interface{}) ``` 当程序终止时,会打印传入 `panic` 的参数。 ```go package main func main() { panic("panic error") } ``` 运行上面的程序,会打印出传入 `panic` 函数的信息,并打印出堆栈跟踪: ```go panic: panic error ``` #### 3.3 发生 panic 时的 defer 上面已经提到了,当函数发生 `panic` 时,它会终止运行,在执行完所有的延迟函数后,程序返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 `panic` 信息,接着打印出堆栈跟踪,最后程序终止。 **示例** ```go package main import "fmt" func myTest() { defer fmt.Println("defer myTest") panic("panic myTest") } func main() { defer fmt.Println("defer main") myTest() } ``` **运行结果** ``` defer myTest defer main panic: panic myTest ``` #### 3.4 recover Go语言推荐使用recover函数将内部异常转为错误处理,这使得用户可以真正的关心业务相关的错误处理。 `recover` 是一个内建函数,用于重新获得 `panic` 协程的控制。 **下面是内建函数 `recover` 的签名:** ```go func recover() interface{} ``` `recover` 必须在 `defer` 函数中才能生效,在其他作用域下,它是不工作的。在延迟函数内调用 `recover` ,可以取到 `panic` 的错误信息,并且停止 `panic` 续发事件,程序运行恢复正常。 **示例** ```go package main import "fmt" func outOfArray(x int) { defer func() { // recover() 可以将捕获到的 panic 信息打印 if err := recover(); err != nil { fmt.Println(err) } }() var array [5]int array[x] = 1 } func main() { // 故意制造数组越界 触发 panic outOfArray(20) // 如果能执行到这句 说明 panic 被捕获了 // 后续的程序能继续运行 fmt.Println("main...") } ``` 虽然该程序触发了 `panic` ,但由于我们使用了 `recover()` 捕获了 `panic` 异常,并输出 `panic` 信息,即使 `panic` 会导致整个程序退出,但在退出前,有 `defer` 延迟函数,还是得执行完 `defer` 。 **然后程序还会继续执行下去:** ``` runtime error: index out of range [20] with length 5 main... ``` 这里要注意一点,只有在相同的协程中调用 `recover` 才管用, `recover` 不能恢复一个不同协程的 `panic` 。 *附参考原文链接* *http://www.go-edu.cn/2022/05/27/go-18-%E9%94%99%E8%AF%AF%E4%B8%8E%E5%BC%82%E5%B8%B8/*
标签:
Golang基础
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2267.html
上一篇
29.Golang写入文件
下一篇
01.Beego框架介绍
评论已关闭
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
4
标签云
VUE
Filter
SQL练习题
Spark SQL
GET和POST
稀疏数组
机器学习
序列化和反序列化
BurpSuite
工具
Typora
ClickHouse
Quartz
JavaWEB项目搭建
SpringCloud
Flink
Golang
MySQL
gorm
数据结构和算法
ajax
随笔
锁
Livy
Spark
Docker
链表
Hbase
JVM
查找
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭