Go 错误处理
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:
type error interface { Error() string }
可以在编码中通过实现 error 接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:
func Sqrt(f float64) (float64, error) { if f < 0 { return 0, errors.New("math: square root of negative number") } // 实现 }
在下面的例子中,在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:
result, err:= Sqrt(-1) if err != nil { fmt.Println(err) }
实例
实例
package main
import (
"fmt"
)
// 定义一个 DivideError 结构
type DivideError struct {
dividee int
divider int
}
// 实现 `error` 接口
func (de *DivideError) Error() string {
strFormat := `
Cannot proceed, the divider is zero.
dividee: %d
divider: 0
`
return fmt.Sprintf(strFormat, de.dividee)
}
// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivideError{
dividee: varDividee,
divider: varDivider,
}
errorMsg = dData.Error()
return
} else {
return varDividee / varDivider, ""
}
}
func main() {
// 正常情况
if result, errorMsg := Divide(100, 10); errorMsg == "" {
fmt.Println("100/10 = ", result)
}
// 当被除数为零的时候会返回错误信息
if _, errorMsg := Divide(100, 0); errorMsg != "" {
fmt.Println("errorMsg is: ", errorMsg)
}
}
import (
"fmt"
)
// 定义一个 DivideError 结构
type DivideError struct {
dividee int
divider int
}
// 实现 `error` 接口
func (de *DivideError) Error() string {
strFormat := `
Cannot proceed, the divider is zero.
dividee: %d
divider: 0
`
return fmt.Sprintf(strFormat, de.dividee)
}
// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivideError{
dividee: varDividee,
divider: varDivider,
}
errorMsg = dData.Error()
return
} else {
return varDividee / varDivider, ""
}
}
func main() {
// 正常情况
if result, errorMsg := Divide(100, 10); errorMsg == "" {
fmt.Println("100/10 = ", result)
}
// 当被除数为零的时候会返回错误信息
if _, errorMsg := Divide(100, 0); errorMsg != "" {
fmt.Println("errorMsg is: ", errorMsg)
}
}
执行以上程序,输出结果为:
100/10 = 10 errorMsg is: Cannot proceed, the divider is zero. dividee: 100 divider: 0
大王叫我来巡山1998
异常处理
Go语言追求简洁优雅 所以 不支持传统的 try…catch…finally 这种异常
Go语言的设计者认为 将异常与控制结构混在一起 使 代码变得混乱
因为开发者 容易滥用异常 甚至一个小小的错误都抛出一个异常
Go语言 用多值返回 来返回错误 不用异常代替错误 更不要用来 控制流程
在极个别的情况下 才使用Go中引入的 Exception 处理 defer panic recover
panic
1 内建函数
2 假如函数F 书写 panic语句 会终止其后要执行的代码 在panic所在函数F内 如果存在要执行的 defer函数列表 按照 defer 逆序执行
3 返回函数F的调用者G 在G中 调用函数F语句之后的代码不会执行 假如函数G中存在要执行的defer函数列表 按照defer的逆序执行 这里的 defer 有点类似 try-catch-finally 中的 finally
4 直到goroutine整个退出 并报告错误
recover
1 内建函数
2 用来控制一个 goroutine 的 panicking 行为 捕获 panic 从而影响应用的行为
3 一般的调用建议
a) 在defer函数中 通过 recever 来终止一个 gojroutine 的 panicking 过程 从而恢复正常代码的执行
b) 可以获取通过 panic 传递的 error
简单 讲
Go语言 可以抛出一个 panic 异常 然后在 defer 中通过 recover 捕获这个异常 然后正常处理
示例
main 函数相当于调用者 G
f函数相当于函数F
func main() {
fmt.Println("c")
defer func() { // 必须要先声明 defer 否则不能捕获到 panic 异常
fmt.Println("d")
if err := recover(); err != nil {
fmt.Println(err) // 这里的 err 其实 是 panic 传入的内容
}
fmt.Println("e")
}()
f() //开始调用f
fmt.Println("f") //这里开始下面代码不会再执行
}
func f() {
fmt.Println("a")
panic("异常信息")
fmt.Println("b") //这里开始下面代码不会再执行
}
-------output-------
c
a
d
异常信息
e
利用 recover 处理 panic 指令 defer 必须在 panic 之前声明 否则当 panic 时 recover 无法捕获到 panic
package main
import (
"fmt"
)
func main() {
defer func() {
fmt.Println("1")
}()
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
panic("fault")
fmt.Println("2")
}
运行结果
fault
1
程序首先运行 panic 出现故障 跳转到包含 recover() 的 defer 函数执行 recover 捕获 panic 此时 panic 就不继续传递
但是 recover 后 程序并 不 返回到 panic 点继续执行以后的动作 而是在 recover 这个点继续执行以后的动作 即执行上面的 defer 函数 输出 1
利用 recover 处理 panic 指令 必须利用 defer 在 panic 之前声明 否则当 panic 时 recover 无法捕获到 panic 无法防止 panic 扩散
大王叫我来巡山1998
回收处理
GO 中 defer 代码块 在函数调用链表中 增加一个函数调用 这个函数调用不是普通的函数调用 是会在函数正常返回 也就是return 后添加一个函数调用 因此 defer通常用来释放函数内部变量
示例
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()
dst, err := os.Create(dstName)
if err != nil {
return
}
defer dst.Close()
return io.Copy(dst, src)
}
通过 defer 在代码中 关闭/清理 代码 所 用 变量 defer作为golang清理变量的特性 有 独有且明确的行为
defer 三条使用规则
当defer被声明时 其参数就会被实时解析 即defer的参数的变量值 在代码中defer使用后的位置改变并不会对改变defer用到的值
defer执行顺序为先进后出 在函数中 先定义的defer将会后执行
defer可以读取有名返回值
defer代码块的作用域仍然在函数之内 因此defer仍然可以读取函数内的变量
文人墨客
个人多次试验,总结几点 panic,defer 和 recover。
代码执行的结果:
文人墨客
fmt.Println 打印结构体的时候,会把其中的 error 的返回的信息打印出来。
结果:
文人墨客
等价于:
文人墨客
这里应该介绍一下 panic 与 recover,一个用于主动抛出错误,一个用于捕获panic抛出的错误。
概念
panic 与 recover 是 Go 的两个内置函数,这两个内置函数用于处理 Go 运行时的错误,panic 用于主动抛出错误,recover 用来捕获 panic 抛出的错误。
panic
有两种情况,一是程序主动调用,二是程序产生运行时错误,由运行时检测并退出。panic
后,程序会从调用panic
的函数位置或发生panic
的地方立即返回,逐层向上执行函数的defer
语句,然后逐层打印函数调用堆栈,直到被recover
捕获或运行到最外层函数。panic
不但可以在函数正常流程中抛出,在defer
逻辑里也可以再次调用panic
或抛出panic
。defer
里面的panic
能够被后续执行的defer
捕获。recover
用来捕获panic
,阻止panic
继续向上传递。recover()
和defer
一起使用,但是defer
只有在后面的函数体内直接被掉用才能捕获panic
来终止异常,否则返回nil
,异常继续向外传递。例子1
例子2
多个panic只会捕捉最后一个:
使用场景
一般情况下有两种情况用到: