Golang被诟病很是多的一点就是缺乏强大方便的异常处理机制,大部分高级编程语言,好比Java、PHP、Python等都拥有一种try catch机制,这种异常捕获机制能够很是方便的处理程序运行中可能出现的各类意外状况。编程
严格来讲,在Go里面,错误和异常是2种不一样的类型,错误通常是指程序产生的逻辑错误,或者意料之中的意外状况,并且异常通常就是panic,好比角标越界、段错误。编程语言
对于错误,Golang采用了一种很是原始的手段,咱们必须手动处理可能产生的每个错误,通常会把错误返回给调用方,下面这种写法在Go里面十分常见:ide
package main
import (
"errors"
"fmt"
)
func main() {
s, err := say()
if err != nil {
fmt.Printf("%s\n", err.Error())
} else {
fmt.Printf("%s\n", s)
}
}
func say() (string, error) {
// do something
return "", errors.New("something error")
}
复制代码
这种写法最大的问题就是每个error都须要判断处理,很是繁琐,若是使用try catch机制,咱们就能够统一针对多个函数调用可能产生的错误作处理,节省一点代码和时间。不过我们今天不是来讨论Go的异常错误处理机制的,这里只是简单说一下。函数
通常错误都是显示的,程序明确返回的,而异常每每是隐示的,不可预测的,好比下面的代码:性能
package main
import "fmt"
func main() {
fmt.Printf("%d\n", cal(1,2))
fmt.Printf("%d\n", cal(5,2))
fmt.Printf("%d\n", cal(5,0)) //panic: runtime error: integer divide by zero
fmt.Printf("%d\n", cal(9,5))
}
func cal(a, b int) int {
return a / b
}
复制代码
在执行第三个计算的时候会发生一个panic,这种错误会致使程序退出,下面的代码的就没法执行了。固然你能够说这种错误理论上是能够预测的,咱们只要在cal函数内部作好处理就好了。ui
然而实际开发中,会发生panic的地方可能特别多,并且不是这种一眼就能看出来的,在Web服务中,这样的panic会致使整个Web服务挂掉,特别危险。spa
虽然没有try catch机制,Go其实有一种相似的recover机制,功能弱了点,用法很简单:日志
package main
import "fmt"
func main() {
fmt.Printf("%d\n", cal(1, 2))
fmt.Printf("%d\n", cal(5, 2))
fmt.Printf("%d\n", cal(5, 0))
fmt.Printf("%d\n", cal(9, 2))
}
func cal(a, b int) int {
defer func() {
if err := recover(); err != nil {
fmt.Printf("%s\n", err)
}
}()
return a / b
}
复制代码
首先,你们得理解defer的做用,简单说defer就相似于面向对象里面的析构函数,在这个函数终止的时候会执行,即便是panic致使的终止。code
因此,在cal函数里面每次终止的时候都会检查有没有异常产生,若是产生了咱们能够处理,好比说记录日志,这样程序还能够继续执行下去。协程
通常defer recover这种机制常常用在常驻进程的应用,好比Web服务,在Go里面,每个Web请求都会分配一个goroutine去处理,在没有作任何处理的状况下,假如某一个请求发生了panic,就会致使整个服务挂掉,这是不可接受的,因此在Web应用里面必须使用recover保证即便某一个请求发生错误也不影响其它请求。
这里我使用一小段代码模拟一下:
package main
import (
"fmt"
)
func main() {
requests := []int{12, 2, 3, 41, 5, 6, 1, 12, 3, 4, 2, 31}
for n := range requests {
go run(n) //开启多个协程
}
for {
select {}
}
}
func run(num int) {
//模拟请求错误
if num%5 == 0 {
panic("请求出错")
}
fmt.Printf("%d\n", num)
}
复制代码
上面这段代码没法完整执行下去,由于其中某一个协程必然会发生panic,从而致使整个应用挂掉,其它协程也中止执行。
解决方法和上面同样,咱们只须要在run函数里面加入defer recover,整个程序就会很是健壮,即便发生panic,也会完整的执行下去。
func run(num int) {
defer func() {
if err := recover();err != nil {
fmt.Printf("%s\n", err)
}
}()
if num%5 == 0 {
panic("请求出错")
}
fmt.Printf("%d\n", num)
}
复制代码
上面的代码只是演示,真正的坑是:若是你在run函数里面又启动了其它协程,这个协程发生的panic是没法被recover的,仍是会致使整个进程挂掉,咱们改造了一下上面的例子:
func run(num int) {
defer func() {
if err := recover(); err != nil {
fmt.Printf("%s\n", err)
}
}()
if num%5 == 0 {
panic("请求出错")
}
go myPrint(num)
}
func myPrint(num int) {
if num%4 == 0 {
panic("请求又出错了")
}
fmt.Printf("%d\n", num)
}
复制代码
我在run函数里面又经过协程的方式调用了另外一个函数,而这个函数也会发生panic,你会发现整个程序也挂了,即便run函数有recover也没有任何做用,这意味着咱们还须要在myPrint函数里面加入recover。可是若是你不使用协程的方式调用myPrint函数,直接调用的话仍是能够捕获recover的。
总结一下就是defer recover这种机制只是针对当前函数和以及直接调用的函数可能产生的panic,它没法处理其调用产生的其它协程的panic,这一点和try catch机制不同。
理论上讲,全部使用协程的地方都必须作defer recover处理,这样才能保证你的应用万无一失,不过开发中能够根据实际状况而定,对于一些不可能出错的函数加了还影响性能。
Go的Web服务也是同样,默认的recover机制只能捕获一层,若是你在这个请求的处理中又使用了其它协程,那么必须很是慎重,毕竟只要发生一个panic,整个Web服务就会挂掉。
最后,总结一下,Go的异常处理机制虽然没有不少其它语言高效,可是基本上仍是能知足需求,目前官方已经在着完善这一点,Go2可能会见到。