李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
GO
正文
30.Golang之错误和异常处理
Leefs
2022-07-25 PM
640℃
0条
[TOC] ### 前言 Go语言没有类似 Java 或 .NET 中的异常处理机制,虽然可以使用 defer、panic、recover 模拟,但官方并不主张这样做,Go语言的设计者认为其他语言的异常机制已被过度使用,上层逻辑需要为函数发生的异常付出太多的资源,同时,如果函数使用者觉得错误处理很麻烦而忽略错误,那么程序将在不可预知的时刻崩溃。 Go语言希望开发者将错误处理视为正常开发必须实现的环节,正确地处理每一个可能发生错误的函数,同时,Go语言使用返回值返回错误的机制,也能大幅降低编译器、运行时处理错误的复杂度,让开发者真正地掌握错误的处理。  ### 一、错误和异常 + **错误指的是可能出现问题的地方出现了问题。** 比如打开一个文件时失败,这种情况在人们的意料之中 。 + **异常指的是不应该出现问题的地方出现了问题。** 比如引用了空指针,这种情况在人们的意料之外。 错误是业务过程的一部分,而异常不是 。 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
40
标签云
Http
SpringBoot
前端
国产数据库改造
Golang基础
工具
设计模式
稀疏数组
Beego
线程池
并发编程
队列
Linux
NIO
Livy
Docker
Hadoop
SQL练习题
高并发
RSA加解密
微服务
Spark Core
HDFS
查找
Netty
JavaWeb
算法
DataWarehouse
持有对象
Sentinel
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞