Golang 語言怎么避免引發 Panic?
01介紹
在 Golang 語言中,程序引發 panic 會導致程序崩潰,所以我們在程序開發時,需要特別小心,避免引發 panic。本文我們介紹 Golang 語言中比較容易引發 panic 的操作。
02指針
任意一種編程語言都會使用函數,我們使用 Golang 編寫函數或方法時,經常會用到指針類型的返回值,這時如果執行調用空指針(指針未初始化或值為 nil),對于新手而言,就很容易引發程序 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")
- }
- }
我們閱讀上面這段代碼,這是一段非常簡單的返回值為指針類型的示例代碼,讀者朋友們試想一下。
如果 GetInfo 方法體中的 data 的值來源于調用另外一個函數或方法,被調用的這個函數或方法返回值是 nil,而我們 main 函數中會使用返回值的 Age 字段作為判定條件,這時程序就會引發 panic,導致程序崩潰。
所以,我們在使用指針類型時,要特別小心,不然我們就只能在調用函數或方法之前,使用 defer 和 recover 添加一段補償代碼,我個人感覺不是很優雅。
- defer func() {
- if err := recover(); err != nil {
- fmt.Println("err = ", err)
- }
- }()
我一般是在判定指針類型的返回值時,為了避免程序引發 panic,我會加一個且(&&)的判定條件,判定返回值不是 nil,并且返回值的某個字段符合某種條件。
- func main() {
- ...
- if userInfo != nil && userInfo.Age >= 18 {
- fmt.Println("this is a man")
- }
- }
03數組和切片
數組和切片類型,當我們越界訪問時,也會引發 panic,導致程序崩潰。不過,一般 IDE 可以提示數組越界訪問的錯誤,如果讀者朋友使用的編輯器不會提示數組越界的錯誤,那你使用數組也要小心了。
- 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通道
如果我們關閉未初始化的通道,重復關閉通道,向已經關閉的通道中發送數據,這三種情況也會引發 panic,導致程序崩潰。
- func main() {
- var ch chan int
- // close(ch)
- ch = make(chan int, 1)
- ch <- 1
- // close(ch)
- // close(ch)
- ch <- 2
- }
05映射
如果我們直接操作未初始化的映射(map),也會引發 panic,導致程序崩潰。
- func main() {
- var m map[string]int
- // m = make(map[string]int)
- m["php"] = 80
- }
另外,操作映射可能會遇到的更為嚴重的一個問題是,同時對同一個映射并發讀寫,它會觸發 runtime.throw,不像 panic 可以使用 recover 捕獲。所以,我們在對同一個映射并發讀寫時,一定要使用鎖。
- 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)
- }
輸出結果:
- 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類型斷言
如果類型斷言使用不當,比如我們不接收布爾值的話,類型斷言失敗也會引發 panic,導致程序崩潰。
- func main() {
- var name interface{} = "frank"
- // a, ok := name.(int)
- // fmt.Println(a, ok)
- a := name.(int)
- fmt.Println(a)
- }
07總結
本文我們介紹 Golang 語言中容易引發 panic 的場景,尤其是空指針操作是最容易踩坑的場景,我們在程序開發中,一定要小心使用指針類型。