Golang 語言怎么避免引發(fā) Panic?
01介紹
在 Golang 語言中,程序引發(fā) panic 會(huì)導(dǎo)致程序崩潰,所以我們?cè)诔绦蜷_發(fā)時(shí),需要特別小心,避免引發(fā) panic。本文我們介紹 Golang 語言中比較容易引發(fā) panic 的操作。
02指針
任意一種編程語言都會(huì)使用函數(shù),我們使用 Golang 編寫函數(shù)或方法時(shí),經(jīng)常會(huì)用到指針類型的返回值,這時(shí)如果執(zhí)行調(diào)用空指針(指針未初始化或值為 nil),對(duì)于新手而言,就很容易引發(fā)程序 panic。
- type User struct {
- Name string
- Age int
- }
- func (u *User) GetInfo() (data *User) {
- data = &User{
- Name: "frank",
- Age: 18,
- }
- return data
- }
- func main() {
- user := new(User)
- userInfo := user.GetInfo()
- fmt.Println(userInfo)
- if userInfo.Age >= 18 {
- fmt.Println("this is a man")
- }
- }
我們閱讀上面這段代碼,這是一段非常簡單的返回值為指針類型的示例代碼,讀者朋友們?cè)囅胍幌隆?/p>
如果 GetInfo 方法體中的 data 的值來源于調(diào)用另外一個(gè)函數(shù)或方法,被調(diào)用的這個(gè)函數(shù)或方法返回值是 nil,而我們 main 函數(shù)中會(huì)使用返回值的 Age 字段作為判定條件,這時(shí)程序就會(huì)引發(fā) panic,導(dǎo)致程序崩潰。
所以,我們?cè)谑褂弥羔橆愋蜁r(shí),要特別小心,不然我們就只能在調(diào)用函數(shù)或方法之前,使用 defer 和 recover 添加一段補(bǔ)償代碼,我個(gè)人感覺不是很優(yōu)雅。
- defer func() {
- if err := recover(); err != nil {
- fmt.Println("err = ", err)
- }
- }()
我一般是在判定指針類型的返回值時(shí),為了避免程序引發(fā) panic,我會(huì)加一個(gè)且(&&)的判定條件,判定返回值不是 nil,并且返回值的某個(gè)字段符合某種條件。
- func main() {
- ...
- if userInfo != nil && userInfo.Age >= 18 {
- fmt.Println("this is a man")
- }
- }
03數(shù)組和切片
數(shù)組和切片類型,當(dāng)我們?cè)浇缭L問時(shí),也會(huì)引發(fā) panic,導(dǎo)致程序崩潰。不過,一般 IDE 可以提示數(shù)組越界訪問的錯(cuò)誤,如果讀者朋友使用的編輯器不會(huì)提示數(shù)組越界的錯(cuò)誤,那你使用數(shù)組也要小心了。
- func main() {
- code := []string{"php", "golang"}
- fmt.Printf("len:%d cap:%d val:%s \n", len(code), cap(code), code)
- fmt.Println(code[2])
- }
04通道
如果我們關(guān)閉未初始化的通道,重復(fù)關(guān)閉通道,向已經(jīng)關(guān)閉的通道中發(fā)送數(shù)據(jù),這三種情況也會(huì)引發(fā) panic,導(dǎo)致程序崩潰。
- func main() {
- var ch chan int
- // close(ch)
- ch = make(chan int, 1)
- ch <- 1
- // close(ch)
- // close(ch)
- ch <- 2
- }
05映射
如果我們直接操作未初始化的映射(map),也會(huì)引發(fā) panic,導(dǎo)致程序崩潰。
- func main() {
- var m map[string]int
- // m = make(map[string]int)
- m["php"] = 80
- }
另外,操作映射可能會(huì)遇到的更為嚴(yán)重的一個(gè)問題是,同時(shí)對(duì)同一個(gè)映射并發(fā)讀寫,它會(huì)觸發(fā) runtime.throw,不像 panic 可以使用 recover 捕獲。所以,我們?cè)趯?duì)同一個(gè)映射并發(fā)讀寫時(shí),一定要使用鎖。
- func main() {
- var m map[string]int
- m = make(map[string]int)
- go func(map[string]int) {
- for {
- m["php"] = 80
- }
- }(m)
- go func(map[string]int) {
- for {
- _ = m["php"]
- }
- }(m)
- time.Sleep(time.Second)
- }
輸出結(jié)果:
- fatal error: concurrent map read and map write
- goroutine 7 [running]:
- runtime.throw({0x10a7510, 0x0})
- /usr/local/opt/go/libexec/src/runtime/panic.go:1198 +0x71 fp=0xc000050f28 sp=0xc000050ef8 pc=0x102fa51
06類型斷言
如果類型斷言使用不當(dāng),比如我們不接收布爾值的話,類型斷言失敗也會(huì)引發(fā) panic,導(dǎo)致程序崩潰。
- func main() {
- var name interface{} = "frank"
- // a, ok := name.(int)
- // fmt.Println(a, ok)
- a := name.(int)
- fmt.Println(a)
- }
07總結(jié)
本文我們介紹 Golang 語言中容易引發(fā) panic 的場(chǎng)景,尤其是空指針操作是最容易踩坑的場(chǎng)景,我們?cè)诔绦蜷_發(fā)中,一定要小心使用指針類型。