Go语言本身没有try/catch异常机制,因为Go的三位创始人在设计Go语言之出觉得这样写会变得很繁琐。
但因为:Go本身支持函数多返回值,因此在写函数的时候,可以优先考虑容错处理。
接下来,我们来看看在Go语言中如何做容错处理。
try/catch
异常机制。type error interface {
Error() string
}
var xxxError = errors.New("xxxxx") // 快速创建错误类型
我们把之前写的Fibonacci的例子加上容错处理,就变成了下面这样。
函数添加了多返回值,最后一个返回error。
若error有值,说明有异常;
若error无值,说明程序正常。
var LessThanTwoError = errors.New("n shoule not less than 2") // 定义错误类型
func GetFibonacci(n int) ([]int, error) {
// 容错处理
if n <= 2 {
return nil, LessThanTwoError
}
fibList := []int{1, 1}
for i := 2; i < n; i++ {
fibList = append(fibList, fibList[i-2]+fibList[i-1])
}
return fibList, nil
}
func TestGetFibonacci(t *testing.T) {
if value, err := GetFibonacci(0); err != nil {
if err == LessThanTwoError {
fmt.Println("It is less error.")
}
t.Error(err)
} else {
t.Log(value)
}
}
代码运行结果如下:
=== RUN?? TestGetFibonacci
It is less error.
??? recover_test.go:65: n shoule not less than 2
--- FAIL: TestGetFibonacci (0.00s)FAIL
panic:用于发送不可恢复的错误,执行defer func
内的代码块,并请求退出程序。
recover:用于恢复panic
抛出的错误。
os.Exit:用于直接退出程序。
func TestPanic(t *testing.T) {
defer func() {
fmt.Println("Finally!")
}()
fmt.Println("Test panic is Started.")
panic(errors.New("Something wrong!"))
}
测试不通过且抛出异常,输出如下:
=== RUN?? TestPanic
Test panic is Started.
Finally!
--- FAIL: TestPanic (0.00s)
panic: Something wrong! [recovered]
?? ?panic: Something wrong!goroutine 19 [running]:
testing.tRunner.func1.2({0xe195a0, 0xc00008a250})
?? ?D:/Program Files/Go/src/testing/testing.go:1545 +0x238
testing.tRunner.func1()
?? ?D:/Program Files/Go/src/testing/testing.go:1548 +0x397
panic({0xe195a0?, 0xc00008a250?})
?? ?D:/Program Files/Go/src/runtime/panic.go:914 +0x21f
command-line-arguments.TestPanic(0x0?)
?? ?D:/golang/ch14/recover_test.go:26 +0x9d
testing.tRunner(0xc000084820, 0xe47e38)
?? ?D:/Program Files/Go/src/testing/testing.go:1595 +0xff
created by testing.(*T).Run in goroutine 1
?? ?D:/Program Files/Go/src/testing/testing.go:1648 +0x3ad
进程 已完成,退出代码为 1
os.Exit
的例子其实,os.Exit
也可以退出程序。
func TestOsExit(t *testing.T) {
fmt.Println("Test os.Exit is Started.")
os.Exit(0)
}
输出如下:
=== RUN?? TestOsExit
Test os.Exit is Started.
--- FAIL: TestOsExit (0.00s)
panic: unexpected call to os.Exit(0) during test [recovered]
?? ?panic: unexpected call to os.Exit(0) during testgoroutine 6 [running]:
testing.tRunner.func1.2({0x7036a0, 0x75c0a0})
?? ?D:/Program Files/Go/src/testing/testing.go:1545 +0x238
testing.tRunner.func1()
?? ?D:/Program Files/Go/src/testing/testing.go:1548 +0x397
panic({0x7036a0?, 0x75c0a0?})
?? ?D:/Program Files/Go/src/runtime/panic.go:914 +0x21f
os.Exit(0x0)
?? ?D:/Program Files/Go/src/os/proc.go:67 +0x51
command-line-arguments.TestOsExit(0x0?)
?? ?D:/golang/ch14/recover_test.go:42 +0x53
testing.tRunner(0xc000045520, 0x737e78)
?? ?D:/Program Files/Go/src/testing/testing.go:1595 +0xff
created by testing.(*T).Run in goroutine 1
?? ?D:/Program Files/Go/src/testing/testing.go:1648 +0x3ad
进程 已完成,退出代码为 1
panic
与os.Exit
究竟有什么区别?os.Exit
退出程序时不会先调用defer func
代码块。os.Exit
退出程序时不会输出当前调用栈信息。
那么,如果我们就是想让程序不crash
,有没有办法呢?
答案是有的,使用recover
,但是很不推荐这么使用recover
。
因为并没有解决发生panic
的问题,只是把错误移除,这样是很不安全的。
甚至,如果是因为系统资源panic
,这样我们的服务就变成了僵尸服务,虽然活着但无法提供服务功能。
recover
使用方式如下,但一般不推荐使用。
func TestPanicRecover(t *testing.T) {
defer func() {
if err := recover(); err != nil { // 恢复错误
fmt.Println("recover panic", err)
}
}()
fmt.Println("Test panic is Started.")
panic(errors.New("Something wrong!"))
}
测试居然通过了,输出如下:
=== RUN?? TestPanicRecover
Test panic is Started.
recover panic Something wrong!
--- PASS: TestPanicRecover (0.00s)
PASS
recover
!因为使用recover可能会导致:
形成僵尸服务进程,使安全检查health check
失效。
因为没有crash
,导致提供不确定的服务。
所以“Let it Crash!”,让程序异常时通过重启来恢复而不是通过recover跳过异常,往往是我们恢复不确定性错误的最好方法!