@TOC
<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">
一拂盯、Go panic用法
Go語言追求簡潔優(yōu)雅坟桅,所以群扶,Go語言不支持傳統(tǒng)的 try…catch…finally 這種異常碗旅,因為Go語言的設(shè)計者們認為砚尽,將異常與控制結(jié)構(gòu)混在一起會很容易使得代碼變得混亂拆撼。因為開發(fā)者很容易濫用異常容劳,甚至一個小小的錯誤都拋出一個異常。在Go語言中闸度,使用多值返回來返回錯誤竭贩。不要用異常代替錯誤,更不要用來控制流程筋岛。在極個別的情況下娶视,也就是說,遇到真正的異常的情況下(比如除數(shù)為 0了)睁宰。才使用Go中引入的Exception處理:defer, panic, recover肪获。
這幾個異常的使用場景可以這么簡單描述:Go中可以拋出一個panic的異常,然后在defer中通過recover捕獲這個異常柒傻,然后正常處理孝赫。
package main
import "fmt"
func main(){
fmt.Println("c")
defer func(){ // 必須要先聲明defer,否則不能捕獲到panic異常
fmt.Println("d")
if err:=recover();err!=nil{
fmt.Println(err) // 這里的err其實就是panic傳入的內(nèi)容红符,55
}
fmt.Println("e")
}()
f() //開始調(diào)用f
fmt.Println("f") //這里開始下面代碼不會再執(zhí)行
}
func f(){
fmt.Println("a")
panic("異常信息")
fmt.Println("b") //這里開始下面代碼不會再執(zhí)行
fmt.Println("f")
}
輸出結(jié)果:
c
a
d
異常信息
e
- 內(nèi)建函數(shù)
- 假如函數(shù)F中書寫了panic語句青柄,會終止其后要執(zhí)行的代碼,在panic所在函數(shù)F內(nèi)如果存在要執(zhí)行的defer函數(shù)列表预侯,按照defer的逆序執(zhí)行
比如panic函數(shù)內(nèi)有:
defer 函數(shù)1
defer 函數(shù)2
defer 函數(shù)3
那么執(zhí)行順序就是:
函數(shù)3
函數(shù)2
函數(shù)1
- 返回函數(shù)F的調(diào)用者G致开,在G中,調(diào)用函數(shù)F語句之后的代碼不會執(zhí)行萎馅,假如函數(shù)G中存在要執(zhí)行的defer函數(shù)列表双戳,按照defer的逆序執(zhí)行,這里的defer 有點類似 try-catch-finally 中的 finally
- 到goroutine整個退出糜芳,并報告錯誤
二飒货、recover:
- 內(nèi)建函數(shù)
- 用來控制一個goroutine的panicking行為,捕獲panic峭竣,從而影響應(yīng)用的行為
- 一般的調(diào)用建議(如上面的例子)
a). 在defer函數(shù)中塘辅,通過recever來終止一個gojroutine的panicking過程,從而恢復(fù)正常代碼的執(zhí)行
b). 可以獲取通過panic傳遞的error
簡單來講:go中可以拋出一個panic的異常皆撩,然后在defer中通過recover捕獲這個異常扣墩,然后正常處理。
注意:利用recover處理panic指令,defer必須在panic之前聲明呻惕,否則當(dāng)panic時盘榨,recover無法捕獲到panic.
三、偶爾的 panic 是必要的
Go 是一種安全的語言蟆融,運行時檢查一些嚴重的編程錯誤草巡。例如在你訪問超出 slice 邊界的元素時,這種行為是未定義的型酥,因此 Go 會在運行時 panic山憨。例如下面的小程序。
package main
import (
"fmt"
)
func main() {
s := make([]string, 3)
fmt.Println(s[5])
}
程序?qū)⒔K止于一個運行時 error弥喉。
panic: runtime error: index out of range
goroutine 1 [running]:
main.main()
/tmp/sandbox209906601/main.go:9 +0x40
其他一些會引發(fā) panic 的就是通過值為 nil 的指針訪問結(jié)構(gòu)體的字段郁竟,關(guān)閉已經(jīng)關(guān)閉的 channel 等。怎樣選擇性的 panic 由境?可以通過訪問 slice 時返回 result棚亩,error 兩個值的方式實現(xiàn)。也可以將 slice 的元素賦值給一個可能返回 error 的函數(shù)虏杰,但是這樣會將代碼變復(fù)雜讥蟆。想象一下,寫一個小片段纺阔,foo瘸彤,bar,baz 都只是一個字符串的一個 slice笛钝,實現(xiàn)片段之間的拼接质况。
foo[i] = bar[i] + baz[i]
就會變成下面這樣冗長的代碼:
br, err := bar[i]
if err != nil {
return err
}
bz, err := baz[i]
if err != nil {
return err
}
err := assign_slice_element(foo, i, br + bz)
if err != nil {
return err
}
這不是開玩笑,不同語言處理這樣的方式是不一樣的玻靡。如果 slices/lists/arrays 的指針 i 越界了结榄,在 Python 和 Java 中就會拋出異常。C 中沒有越界檢查囤捻,所以你就可以盡情的蹂躪邊界外的內(nèi)存空間臼朗,最后將導(dǎo)致程序崩潰或者暴露安全漏洞。C++ 中將采用折中的處理方式最蕾。性能優(yōu)先的模塊采用這種不安全的 C 模式依溯,其他模塊(比如 std::vector::at)采用拋出異常的方式老厌。
因為上面重寫的小片段變得如此冗長是不可接受的瘟则。Go 選擇了 panic ,這是一種類似異常的機制枝秤,在代碼中保留了像 bugs 這樣最原始的異常條件醋拧。
這不只是內(nèi)建代碼能夠這樣用,自定義代碼也可以在任何需要的地方調(diào)用 panic。在有些可能導(dǎo)致可怕錯誤的地方還鼓勵使用 panic 拋出 error丹壕,比如 bug 或者一些關(guān)鍵因素被違反的時候庆械。比如在 swich 的某個 case 在當(dāng)前上下文中是不可能發(fā)生的,在這種 case 中只有一個 panic 函數(shù)菌赖。這無形中等價于 Python 中的 raise 或者 C++ 中的 throw缭乘。這也強有力的證明了在捕獲異常方面 Go 的異常處理的特殊之處。
Go 中 panic 恢復(fù)的限制條件
一般就HTTP服務(wù)的全局做一個recover,然后任何地方panic都能全局捕獲相應(yīng)一個錯誤