1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 6179字,需瀏覽 13分鐘

         ·

        2021-08-22 22:46

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

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

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

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

        錯誤處理

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

        Go 中有關(guān)于錯誤處理的標準模式,即 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()
        }

        暫時看不懂也沒有關(guān)系,等學(xué)會了接口之后,再回過頭來看這段代碼,應(yīng)該就豁然開朗了。

        defer

        延遲函數(shù)調(diào)用,defer 后邊會接一個函數(shù),但該函數(shù)不會立刻被執(zhí)行,而是等到包含它的程序返回時(包含它的函數(shù)執(zhí)行了 return 語句、運行到函數(shù)結(jié)尾自動返回、對應(yīng)的 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í)行釋放操作,有可能導(dǎo)致panic
             */

            defer f.Close()
        }

        defer 語句經(jīng)常成對出現(xiàn),比如打開和關(guā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)用關(guān)閉函數(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ù)來捕獲并恢復(fù)。

        感覺挺不好理解的,但仔細想想其實和 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 會向上傳遞,直接導(dǎo)致 G() 異常,然后程序直接退出。

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

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

        總結(jié)

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

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

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

        使用 panicrecover 來拋出錯誤和恢復(fù)。

        使用 panic 一般有兩種情況:

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

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

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


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

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

        關(guān)注公眾號 AlwaysBeta,回復(fù)「goebook」領(lǐng)取 Go 編程經(jīng)典書籍。

        Go 專欄文章列表:

        瀏覽 58
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            亚洲无码在线影院 | 在线看黄色网 | 天堂网在线免费 | 日逼艹逼| 女公安一级毛片 | 日本秋霞电影院午夜伦片A片 | 中文字幕22页 | 91福利社区在线观看 | 熟女乱伦视频 | 欧美一级A片免费看 |