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。
package main import ( "fmt" ) func main() { fmt.Println("外层开始") defer func() { fmt.Println("外层准备recover") if err := recover(); err != nil { fmt.Printf("%#v-%#v\n", "外层", err) // err已经在上一级的函数中捕获了,这里没有异常,只是例行先执行defer,然后执行后面的代码 } else { fmt.Println("外层没做啥事") } fmt.Println("外层完成recover") }() fmt.Println("外层即将异常") f() fmt.Println("外层异常后") defer func() { fmt.Println("外层异常后defer") }() } func f() { fmt.Println("内层开始") defer func() { fmt.Println("内层recover前的defer") }() defer func() { fmt.Println("内层准备recover") if err := recover(); err != nil { fmt.Printf("%#v-%#v\n", "内层", err) // 这里err就是panic传入的内容 } fmt.Println("内层完成recover") }() defer func() { fmt.Println("内层异常前recover后的defer") }() panic("异常信息") defer func() { fmt.Println("内层异常后的defer") }() fmt.Println("内层异常后语句") //recover捕获的一级或者完全不捕获这里开始下面代码不会再执行 }代码执行的结果:
文人墨客
fmt.Println 打印结构体的时候,会把其中的 error 的返回的信息打印出来。
type User struct { username string password string } func (p *User) init(username string ,password string) (*User,string) { if ""==username || ""==password { return p,p.Error() } p.username = username p.password = password return p,""} func (p *User) Error() string { return "Usernam or password shouldn't be empty!"} } func main() { var user User user1, _ :=user.init("",""); fmt.Println(user1) }结果:
文人墨客
if result, errorMsg := Divide(100, 10); errorMsg == "" { fmt.Println("100/10 = ", result) } if _, errorMsg := Divide(100, 0); errorMsg != "" { fmt.Println("errorMsg is: ", errorMsg) }等价于:
result, errorMsg := Divide(100,10) if errorMsg == ""{ fmt.Println("100/10 = ", result) } result, errorMsg = Divide(100,0) if errorMsg != ""{ fmt.Println("errorMsg is: ", errorMsg) }文人墨客
这里应该介绍一下 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
//以下捕获失败 defer recover() defer fmt.Prinntln(recover) defer func(){ func(){ recover() //无效,嵌套两层 }() }() //以下捕获有效 defer func(){ recover() }() func except(){ recover() } func test(){ defer except() panic("runtime error") }例子2
多个panic只会捕捉最后一个:
package main import "fmt" func main(){ defer func(){ if err := recover() ; err != nil { fmt.Println(err) } }() defer func(){ panic("three") }() defer func(){ panic("two") }() panic("one") }使用场景
一般情况下有两种情况用到: