<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go 專欄|錯誤處理:defer,panic 和 recover

          共 6179字,需瀏覽 13分鐘

           ·

          2021-08-22 22:46

          最近校招又開始了,我也接到了一些面試工作,當我問「你覺得自己有什么優(yōu)勢」時,十個人里有八個的回答里會有一條「精力充沛,能加班」。

          怪不得國家都給認證了:新生代農(nóng)民工。合著我們這根本就不是什么腦力勞動者,而是靠出賣體力的苦勞力。

          好了,廢話不多說,肝文還確實需要體力。

          這篇來說說 Go 的錯誤處理。

          錯誤處理

          錯誤處理相當重要,合理地拋出并記錄錯誤能在排查問題時起到事半功倍的作用。

          Go 中有關于錯誤處理的標準模式,即 error 接口,定義如下:

          type error interface {
              Error() string
          }

          大部分函數(shù),如果需要返回錯誤的話,基本都會將 error 作為多個返回值的最后一個,舉個例子:

          package main

          import "fmt"

          func main() {
              n, err := echo(10)
              if err != nil {
                  fmt.Println("error: " + err.Error())
              } else {
                  fmt.Println(n)
              }
          }

          func echo(param int) (int, error) {
              return param, nil
          }

          我們也可以使用自定義的 error 類型,比如調(diào)用標準庫的 os.Stat 方法,返回的錯誤就是自定義類型:

          type PathError struct {
              Op   string
              Path string
              Err  error
          }

          func (e *PathError) Error() string {
              return e.Op + " " + e.Path + ": " + e.Err.Error()
          }

          暫時看不懂也沒有關系,等學會了接口之后,再回過頭來看這段代碼,應該就豁然開朗了。

          defer

          延遲函數(shù)調(diào)用,defer 后邊會接一個函數(shù),但該函數(shù)不會立刻被執(zhí)行,而是等到包含它的程序返回時(包含它的函數(shù)執(zhí)行了 return 語句、運行到函數(shù)結尾自動返回、對應的 goroutine panic),defer 函數(shù)才會被執(zhí)行。

          通常用于資源釋放、打印日志、異常捕獲等。

          func main() {
              f, err := os.Open(filename)
              if err != nil {
                  return err
              }
              /**
               * 這里defer要寫在err判斷的后邊而不是os.Open后邊
               * 如果資源沒有獲取成功,就沒有必要對資源執(zhí)行釋放操作
               * 如果err不為nil而執(zhí)行資源執(zhí)行釋放操作,有可能導致panic
               */

              defer f.Close()
          }

          defer 語句經(jīng)常成對出現(xiàn),比如打開和關閉,連接和斷開,加鎖和解鎖。

          defer 語句在 return 語句之后執(zhí)行。

          package main

          import (
              "fmt"
          )

          func main() {
              fmt.Println(triple(4)) // 12
          }

          func double(x int) (result int) {
              defer func() {
                  fmt.Printf("double(%d) = %d\n", x, result)
              }()

              return x + x
          }

          func triple(x int) (result int) {
              defer func() {
                  result += x
              }()

              return double(x)
          }

          切勿在 for 循環(huán)中使用 defer 語句,因為 defer 語句不到函數(shù)的最后一刻是不會執(zhí)行的,所以下面這段代碼很可能會用盡所有文件描述符。

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

          一種解決辦法是將循環(huán)體單獨寫一個函數(shù),這樣每次循環(huán)的時候都會調(diào)用關閉函數(shù)。

          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()
          }

          defer 語句的執(zhí)行是按調(diào)用 defer 語句的倒序執(zhí)行。

          package main

          import (
              "fmt"
          )

          func main() {
              defer func() {
                  fmt.Println("first")
              }()

              defer func() {
                  fmt.Println("second")
              }()

              fmt.Println("done")
          }

          輸出:

          done
          second
          first

          panic 和 recover

          一般情況下,在程序里記錄錯誤日志,就可以幫助我們在碰到異常時快速定位問題。

          但還有一些錯誤比較嚴重的,比如數(shù)組越界訪問,程序會主動調(diào)用 panic 來拋出異常,然后程序退出。

          如果不想程序退出的話,可以使用 recover 函數(shù)來捕獲并恢復。

          感覺挺不好理解的,但仔細想想其實和 try-catch 也沒什么區(qū)別。

          先來看看兩個函數(shù)的定義:

          func panic(interface{})
          func recover() interface
          {}

          panic 參數(shù)類型是 interface{},所以可以接收任意參數(shù)類型,比如:

          panic(404)
          panic("network broken")
          panic(Error("file not exists"))

          recover 需要在 defer 函數(shù)中執(zhí)行,舉個例子:

          package main

          import (
              "fmt"
          )

          func main() {
              G()
          }

          func G() {
              defer func() {
                  fmt.Println("c")
              }()
              F()
              fmt.Println("繼續(xù)執(zhí)行")
          }

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

          輸出:

          捕獲異常: a
          b
          繼續(xù)執(zhí)行
          c

          F() 中拋出異常被捕獲,G() 還可以正常繼續(xù)執(zhí)行。如果 F() 沒有捕獲的話,那么 panic 會向上傳遞,直接導致 G() 異常,然后程序直接退出。

          還有一個場景就是我們自己在調(diào)試程序時,可以使用 panic 來中斷程序,拋出異常,用于排查問題。

          這個就不舉例了,反正是我們自己調(diào)試,怎么爽怎么來就行了。

          總結

          錯誤處理在開發(fā)過程中至關重要,好的錯誤處理可以使程序更加健壯。而且將錯誤信息清晰地記錄日志,在排查問題時非常有用。

          Go 中使用 error 類型進行錯誤處理,還可以在此基礎上自定義錯誤類型。

          使用 defer 語句進行延遲調(diào)用,用來關閉或釋放資源。

          使用 panicrecover 來拋出錯誤和恢復。

          使用 panic 一般有兩種情況:

          1. 程序遇到無法執(zhí)行的錯誤時,主動調(diào)用 panic 結束運行;

          2. 在調(diào)試程序時,主動調(diào)用 panic 結束運行,根據(jù)拋出的錯誤信息來定位問題。

          為了程序的健壯性,可以使用 recover 捕獲錯誤,恢復程序運行。


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

          地址: https://github.com/yongxinz/gopher/tree/main/sc

          關注公眾號 AlwaysBeta,回復「goebook」領取 Go 編程經(jīng)典書籍。

          Go 專欄文章列表:

          瀏覽 58
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  俺去啦俺去也 | 国产精品自产拍在线 | 粉嫩久久久久久久极品 | 国产99自拍 | 人妻天天爽夜夜爽 |