<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      聽說,99% 的 Go 程序員都被 defer 坑過

      原文鏈接: 聽說,99% 的 Go 程序員都被 defer 坑過

      先聲明:我被坑過。

      之前寫 Go 專欄時,寫過一篇文章:Go 專欄|錯誤處理:defer,panic 和 recover。有小伙伴留言說:道理都懂,但還是不知道怎么用,而且還總出現莫名奇妙的問題。

      出問題就對了,這個小東西壞的很,一不留神就出錯。

      所以,面對這種情況,我們今天就不講道理了。直接把我珍藏多年的代碼一把梭,憑借多年踩坑經歷和寫 BUG 經驗,我要站著把這個坑邁過去。

      一、

      先來一個簡單的例子熱熱身:

      package main
      
      import (
          "fmt"
      )
      
      func main() {
          defer func() {
              fmt.Println("first")
          }()
      
          defer func() {
              fmt.Println("second")
          }()
      
          fmt.Println("done")
      }
      

      輸出:

      done
      second
      first
      

      這個比較簡單,defer 語句的執行順序是按調用 defer 語句的倒序執行。

      二、

      看看這段代碼有什么問題?

      for _, filename := range filenames {
          f, err := os.Open(filename)
          if err != nil {
              return err
          }
          defer f.Close()
      }
      

      這段代碼其實很危險,很可能會用盡所有文件描述符。因為 defer 語句不到函數的最后一刻是不會執行的,也就是說文件始終得不到關閉。所以切記,一定不要在 for 循環中使用 defer 語句。

      那怎么優化呢?可以將循環體單獨寫一個函數,這樣每次循環的時候都會調用關閉函數。

      如下:

      for _, filename := range filenames {
          if err := doFile(filename); err != nil {
              return err
          }
      }
      
      func doFile(filename string) error {
          f, err := os.Open(filename)
          if err != nil {
              return err
          }
          defer f.Close()
      }
      

      三、

      看看這三個函數的輸出結果是什么?

      package main
      
      import (
      	"fmt"
      )
      
      func a() (r int) {
      	defer func() {
      		r++
      	}()
      	return 0
      }
      
      func b() (r int) {
      	t := 5
      	defer func() {
      		t = t + 5
      	}()
      	return t
      }
      
      func c() (r int) {
      	defer func(r int) {
      		r = r + 5
      	}(r)
      	return 1
      }
      
      func main() {
      	fmt.Println("a = ", a())
      	fmt.Println("b = ", b())
      	fmt.Println("c = ", c())
      }
      

      公布答案:

      a =  1
      b =  5
      c =  1
      

      你答對了嗎?

      說實話剛開始看到這個結果時,我是相當費解,完全不知道怎么回事。

      但可以看到,這三個函數都有一個共同特點,它們都有一個命名返回值,并且都在函數中引用了這個返回值。

      引用的方式分兩種:分別是閉包和函數參數。

      先看 a() 函數:

      閉包通過 r++ 修改了外部變量,返回值變成了 1。

      相當于:

      func aa() (r int) {
      	r = 0
      	// 在 return 之前,執行 defer 函數
      	func() {
      		r++
      	}()
      	return
      }
      

      再看 b() 函數:

      閉包內修改的只是局部變量 t,而外部變量 t 不受影響,所以還是返回 5。

      相當于:

      func bb() (r int) {
      	t := 5
      	// 賦值
      	r = t
      	// 在 return 之前,執行 defer 函數
      	// defer 函數沒有對返回值 r 進行修改,只是修改了變量 t
      	func() {
      		t = t + 5
      	}()
      	return
      }
      

      最后是 c 函數:

      參數傳遞是值拷貝,實參不受影響,所以還是返回 1。

      相當于:

      func cc() (r int) {
      	// 賦值
      	r = 1
      	// 這里修改的 r 是函數形參的值
      	// 值拷貝,不影響實參值
      	func(r int) {
      		r = r + 5
      	}(r)
      	return
      }
      

      那么,為了避免寫出這么令人意外的代碼,最好在定義函數時就不要使用命名返回值。或者如果使用了,就不要在 defer 中引用。

      再看下面兩個例子:

      func d() int {
      	r := 0
      	defer func() {
      		r++
      	}()
      	return r
      }
      
      func e() int {
      	r := 0
      	defer func(i int) {
      		i++
      	}(r)
      	return 0
      }
      
      d =  0
      e =  0
      

      返回值符合預期,再也不用絞盡腦汁猜了。

      四、

      defer 表達式的函數如果在 panic 后面,則這個函數無法被執行。

      func main() {
          panic("a")
          defer func() {
              fmt.Println("b")
          }()
      }
      

      輸出如下,b 沒有打印出來。

      panic: a
      
      goroutine 1 [running]:
      main.main()
      	xxx.go:87 +0x4ce
      exit status 2
      

      而如果 defer 在前,則可以執行。

      func main() {
      	defer func() {
      		fmt.Println("b")
      	}()
      	panic("a")
      }
      

      輸出:

      b
      panic: a
      
      goroutine 1 [running]:
      main.main()
          xxx.go:90 +0x4e7
      exit status 2
      

      五、

      看看下面這段代碼的執行順序:

      func G() {
      	defer func() {
      		fmt.Println("c")
      	}()
      
      	F()
      	fmt.Println("繼續執行")
      }
      
      func F() {
      	defer func() {
      		if err := recover(); err != nil {
      			fmt.Println("捕獲異常:", err)
      		}
      		fmt.Println("b")
      	}()
      	panic("a")
      }
      
      func main() {
      	G()
      }
      

      順序如下:

      1. 調用 G() 函數;
      2. 調用 F() 函數;
      3. F() 中遇到 panic,立刻終止,不執行 panic 之后的代碼;
      4. 執行 F()defer 函數,遇到 recover 捕獲錯誤,繼續執行 defer 中代碼,然后返回;
      5. 執行 G() 函數后續代碼,最后執行 G()defer 函數。

      輸出:

      捕獲異常: a
      b
      繼續執行
      c
      

      五、

      看看下面這段代碼的執行順序:

      func G() {
      	defer func() {
      		if err := recover(); err != nil {
      			fmt.Println("捕獲異常:", err)
      		}
      		fmt.Println("c")
      	}()
      
      	F()
      	fmt.Println("繼續執行")
      }
      
      func F() {
      	defer func() {
      		fmt.Println("b")
      	}()
      	panic("a")
      }
      
      func main() {
      	G()
      }
      

      順序如下:

      1. 調用 G() 函數;
      2. 調用 F() 函數;
      3. F() 中遇到 panic,立刻終止,不執行 panic 之后的代碼;
      4. 執行 F()defer 函數,由于沒有 recover,則將 panic 拋到 G() 中;
      5. G() 收到 panic 則不會執行后續代碼,直接執行 defer 函數;
      6. defer 中捕獲 F() 拋出的異常 a,然后繼續執行,最后退出。

      輸出:

      b
      捕獲異常: a
      c
      

      六、

      看看下面這段代碼的執行順序:

      func G() {
      	defer func() {
      		fmt.Println("c")
      	}()
      
      	F()
      	fmt.Println("繼續執行")
      }
      
      func F() {
      	defer func() {
      		fmt.Println("b")
      	}()
      	panic("a")
      }
      
      func main() {
      	G()
      }
      

      順序如下:

      1. 調用 G() 函數;
      2. 調用 F() 函數;
      3. F() 中遇到 panic,立刻終止,不執行 panic 之后的代碼;
      4. 執行 F()defer 函數,由于沒有 recover,則將 panic 拋到 G() 中;
      5. G() 收到 panic 則不會執行后續代碼,直接執行 defer 函數;
      6. 由于沒有 recover,直接拋出 F() 拋過來的異常 a,然后退出。

      輸出:

      b
      c
      panic: a
      
      goroutine 1 [running]:
      main.F()
      	xxx.go:90 +0x5b
      main.G()
      	xxx.go:82 +0x48
      main.main()
      	xxx.go:107 +0x4a5
      exit status 2
      

      七、

      看看下面這段代碼的執行順序:

      func G() {
      	defer func() {
      		// goroutine 外進行 recover
      		if err := recover(); err != nil {
      			fmt.Println("捕獲異常:", err)
      		}
      		fmt.Println("c")
      	}()
      
      	// 創建 goroutine 調用 F 函數
      	go F()
      	time.Sleep(time.Second)
      }
      
      func F() {
      	defer func() {
      		fmt.Println("b")
      	}()
      	// goroutine 內部拋出panic
      	panic("a")
      }
      
      func main() {
      	G()
      }
      

      順序如下:

      1. 調用 G() 函數;
      2. 通過 goroutine 調用 F() 函數;
      3. F() 中遇到 panic,立刻終止,不執行 panic 之后的代碼;
      4. 執行 F()defer 函數,由于沒有 recover,則將 panic 拋到 G() 中;
      5. 由于 goroutine 內部沒有進行 recover,則 goroutine 外部函數,也就是 G() 函數是沒辦法捕獲的,程序直接崩潰退出。

      輸出:

      b
      panic: a
      
      goroutine 6 [running]:
      main.F()
      	xxx.go:96 +0x5b
      created by main.G
      	xxx.go:87 +0x57
      exit status 2
      

      八、

      最后再說一個 recover 的返回值問題:

      defer func() {
      	if err := recover(); err != nil {
      		fmt.Println("捕獲異常:", err.Error())
      	}
      }()
      panic("a")
      

      recover 返回的是 interface {} 類型,而不是 error 類型,所以這樣使用的話會報錯:

      err.Error undefined (type interface {} is interface with no methods)
      

      可以這樣來轉換一下:

      defer func() {
      	if err := recover(); err != nil {
      		fmt.Println("捕獲異常:", fmt.Errorf("%v", err).Error())
      	}
      }()
      panic("a")
      

      或者直接打印結果:

      defer func() {
      	if err := recover(); err != nil {
      		fmt.Println("捕獲異常:", err)
      	}
      }()
      panic("a")
      

      輸出:

      捕獲異常: a
      

      以上就是本文的全部內容,其實寫過其他的語言的同學都知道,關閉文件句柄,釋放鎖等操作是很容易忘的。而 Go 語言通過 defer 很好地解決了這個問題,但在使用過程中還是要小心。

      本文總結了一些容踩坑的點,希望能夠幫助大家少寫 BUG,如果大家覺得有用的話,歡迎點贊和轉發。


      文章中的腦圖和源碼都上傳到了 GitHub,有需要的同學可自行下載。

      源碼地址:

      推薦閱讀:

      參考:

      posted @ 2021-10-18 21:53  yongxinz  閱讀(423)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 亚洲产国偷v产偷v自拍色戒| 中文www天堂| 重口SM一区二区三区视频| 久久日产一线二线三线| 最近中文国语字幕在线播放| 国内极度色诱视频网站| 日韩精品久久不卡中文字幕 | 久热这里只精品视频99| 丰满人妻熟妇乱又精品视| 久久久久久久一线毛片| 日本免费视频| 四虎国产精品永久地址99| 亚洲精品国产老熟女久久| 中国熟妇毛多多裸交视频| 上虞市| 九九热在线免费视频观看| 4399理论片午午伦夜理片| 天天爽夜夜爱| 综合色在线| 亚洲大尺度无码专区尤物| 97久久久亚洲综合久久| 麻豆精产国品一二三产| 91网站在线看| 国产天美传媒性色av高清| 人妻丰满熟妇av无码区| 国产L精品国产亚洲区在线观看| 精品久久久久久久久午夜福利| 亚洲av色一区二区三区| 青青青爽在线视频观看| 久久综合久中文字幕青草| 一区二区三区成人| 日韩精品国产二区三区| 中文字幕精品亚洲字幕成| 67194熟妇在线直接进入| 怡春院久久国语视频免费| 92国产精品午夜福利免费| 免费人成自慰网站| 在线a人片免费观看| 日本一区二区三本视频在线观看 | 欧美老熟妇又粗又大| 成人午夜免费无码视频在线观看 |