在实际的项目中,对于异常的最佳实践很多,在使用不同的语言开发不同类型的程序时有不同的建议。Go语言中没有使用try...catch类似的异常处理机制,而是提供了panic和recover函数来处理所谓的运行时异常,也就是所谓的错误处理机制。配合defer语句和error接口开发者可以非常灵活地处理运行时的错误和异常。
代码中随便使用panic显然不是一个好方式,我们知道在风险控制中,有所谓已知的未知和未知的未知,在Go程序设计中,对于前者我们可以通过预先开发的代码分支来处理,对于后者就比较棘手了:
- 如果项目中的代码、使用的标准库以及第三方库在运行时内部捕获了异常并通过合适的error对象返回给调用者,那我们可以尽量少甚至可以不用panic函数。
- 如果无法保证上面的情况,那为了确保程序在运行时不会因为未知的未知导致崩溃,那panic函数可能不得不加在任何需要的地方。
我们应该认识到,panic函数是我们和计算机都不希望看到的,应该在设计开发的时候充分考虑使用场景可能出现的情况,处理好已知的未知,在确定需要的地方使用panic机制。
defer
defer关键字用来标记最后执行的Go语句,一般用在资源释放、关闭连接等操作,会在函数关闭前调用。多个defer的定义与执行类似于栈的操作:先进后出,最先定义的最后执行。
package main
import (
"fmt"
)
func testA() int {
x := 5
defer func() {
x++
fmt.Printf("testA defer func-1[x=>%d]\n", x)
}()
defer func() {
x++
fmt.Printf("testA defer funn-2[x=>%d]\n", x)
}()
return x
}
func testB() (x int) {
x = 5
defer func() {
fmt.Println("testB defer func")
}()
x++
return
}
func main() {
a := testA()
b := testB()
fmt.Printf("testA return %d\n", a)
fmt.Printf("testB return %d\n", b)
}
执行后输出结果:
testA defer funn-2[x=>6]
testA defer func-1[x=>7]
testB defer func
testA return 5
testB return 6
分析如上代码,return xxx语句并不是一条原子指令,在其执行的时候语句会分解成返回变量=xxx和return,最后执行return。之前说defer语句是在函数关闭的时候调用,确切的说是在执行return语句的时候调用,注意是return不是return xxx。在同一个函数里的defer会按照栈的先进后出原则执行。
Error
Go语言通过支持多返回值,让在运行时返回详细的错误信息给调用者变得非常方便。我们可以在编码中通过实现error接口类型来生成错误信息,error接口的定义如下:
type error interface {
Error() string
}
通过下面的例子来看一下error接口的使用:
package main
import (
"fmt"
)
type error interface {
Error() string
}
// DivisionError 类型定义
type DivisionError struct {
dividend int
divider int
}
// Error 方法实现
func (de *DivisionError) Error() string {
strFormat := `
Cannot proceed, the divider is zero.
dividend: %d
divider: 0`
return fmt.Sprintf(strFormat, de.dividend)
}
// Divide 函数定义
func Divide(varDividend int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivisionError{
dividend: varDividend,
divider: varDivider,
}
errorMsg = dData.Error()
} else {
result = varDividend / varDivider
}
return
}
func main() {
result, err := Divide(100, 10)
if err == "" {
fmt.Printf("100/10=%d\n", result)
}
_, err = Divide(100, 0)
if err != "" {
fmt.Printf("errorMsg is: %s\n", err)
}
}
运行后可以看到如下输出:
100/10=10
errorMsg is:
Cannot proceed, the divider is zero.
dividend: 100
divider: 0
panic和recover
panic和recover是两个内置函数,panic函数用于抛出异常,recover函数用于捕获panic函数的参数信息。recover函数只有在defer语句调用的函数中直接调用时才能生效,如果goroutine没有panic,那recover函数会返回nil。
package main
import (
"fmt"
)
// SimplePanicRecover panic/recover简单的例子
func SimplePanicRecover() {
defer func() {
if err := recover(); err != nil {
fmt.Println("Panic info is:", err)
}
}()
panic("SimplePanicRecover panic!")
}
// MultiPanicRecover 多个panic/recover
// 当defer中也调用了panic函数时,最后被调用的panic函数的参数会被后面的recover函数获取到
// 一个函数中可以定义多个defer函数,按照FILO的规则执行
func MultiPanicRecover() {
defer func() {
if err := recover(); err != nil {
fmt.Println("Panic info is:", err)
}
}()
defer func() {
panic("MultiPanicRecover defer inner panic!")
}()
defer func() {
if err := recover(); err != nil {
fmt.Println("Panic info is:", err)
}
}()
panic("MultiPanicRecover panic!")
}
// NoPanicButHasRecover 只有panic没有recover
// 如果函数没有panic函数,调用recover函数不会获取到任何信息,也不会影响当前进程
func NoPanicButHasRecover() {
if err := recover(); err != nil {
fmt.Println("NoPanicButHasRecover Panic info is:", err)
} else {
fmt.Println("NoPanicButHasRecover Panic info is:", err)
}
}
func main() {
SimplePanicRecover()
MultiPanicRecover()
NoPanicButHasRecover()
}
运行后可以看到如下输出:
Panic info is: SimplePanicRecover panic!
Panic info is: MultiPanicRecover panic!
Panic info is: MultiPanicRecover defer inner panic!
NoPanicButHasRecover Panic info is: <nil>