<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 經(jīng)典入門系列 30:錯誤處理

          共 6302字,需瀏覽 13分鐘

           ·

          2020-12-30 21:50

          點擊上方藍色“Go語言中文網(wǎng)”關(guān)注,每天一起學(xué) Go

          歡迎來到 Golang 系列教程[1]的第 30 篇。

          什么是錯誤?

          錯誤表示程序中出現(xiàn)了異常情況。比如當我們試圖打開一個文件時,文件系統(tǒng)里卻并沒有這個文件。這就是異常情況,它用一個錯誤來表示。

          在 Go 中,錯誤一直是很常見的。錯誤用內(nèi)建的 error 類型來表示。

          就像其他的內(nèi)建類型(如 int、float64 等),錯誤值可以存儲在變量里、作為函數(shù)的返回值等等。

          示例

          現(xiàn)在我們開始編寫一個示例,該程序試圖打開一個并不存在的文件。

          package?main

          import?(
          ????"fmt"
          ????"os"
          )

          func?main()?{
          ????f,?err?:=?os.Open("/test.txt")
          ????if?err?!=?nil?{
          ????????fmt.Println(err)
          ????????return
          ????}
          ????fmt.Println(f.Name(),?"opened?successfully")
          }

          在 playground 中運行[2]

          在程序的第 9 行,我們試圖打開路徑為 /test.txt 的文件(playground 顯然并不存在這個文件)。os 包里的 `Open`[3] 函數(shù)有如下簽名:

          func?Open(name?string)?(file?*File,?err?error)

          如果成功打開文件,Open 函數(shù)會返回一個文件句柄(File Handler)和一個值為 nil 的錯誤。而如果打開文件時發(fā)生了錯誤,會返回一個不等于 nil 的錯誤。

          如果一個函數(shù)[4]方法[5] 返回了錯誤,按照慣例,錯誤會作為最后一個值返回。于是 Open 函數(shù)也是將 err 作為最后一個返回值。

          按照 Go 的慣例,在處理錯誤時,通常都是將返回的錯誤與 nil 比較。nil 值表示了沒有錯誤發(fā)生,而非 nil 值表示出現(xiàn)了錯誤。在這里,我們第 10 行檢查了錯誤值是否為 nil。如果不是 nil,我們會簡單地打印出錯誤,并在 main 函數(shù)中返回。

          運行該程序會輸出:

          open?/test.txt:?No?such?file?or?directory

          很棒!我們得到了一個錯誤,它指出該文件并不存在。

          錯誤類型的表示

          讓我們進一步深入,理解 error 類型是如何定義的。error 是一個接口[6]類型,定義如下:

          type?error?interface?{
          ????Error()?string
          }

          error 有了一個簽名為 Error() string 的方法。所有實現(xiàn)該接口的類型都可以當作一個錯誤類型。Error() 方法給出了錯誤的描述。

          fmt.Println 在打印錯誤時,會在內(nèi)部調(diào)用 Error() string 方法來得到該錯誤的描述。上一節(jié)示例中的第 11 行,就是這樣打印出錯誤的描述的。

          從錯誤獲取更多信息的不同方法

          現(xiàn)在,我們知道了 error 是一個接口類型,讓我們看看如何從一個錯誤獲取更多信息。

          在前面的示例里,我們只是打印出錯誤的描述。如果我們想知道這個錯誤的文件路徑,該怎么做呢?一種選擇是直接解析錯誤的字符串。這是前面示例的輸出:

          open?/test.txt:?No?such?file?or?directory

          我們解析了這條錯誤信息,雖然獲取了發(fā)生錯誤的文件路徑,但是這種方法很不優(yōu)雅。隨著語言版本的更新,這條錯誤的描述隨時都有可能變化,使我們程序出錯。

          有沒有更加可靠的方法來獲取文件名呢?答案是肯定的,這是可以做到的,Go 標準庫給出了各種提取錯誤相關(guān)信息的方法。我們一個個來看看吧。

          1. 斷言底層結(jié)構(gòu)體類型,使用結(jié)構(gòu)體字段獲取更多信息

          如果你仔細閱讀了 `Open`[7] 函數(shù)的文檔,你可以看見它返回的錯誤類型是 *PathError`PathError`[8]結(jié)構(gòu)體[9]類型,它在標準庫中的實現(xiàn)如下:

          type?PathError?struct?{
          ????Op???string
          ????Path?string
          ????Err??error
          }

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

          如果你有興趣了解上述源代碼出現(xiàn)的位置,可以在這里找到:https://golang.org/src/os/error.go?s=653:716#L11。

          通過上面的代碼,你就知道了 *PathError 通過聲明 Error() string 方法,實現(xiàn)了 error 接口。Error() string 將文件操作、路徑和實際錯誤拼接,并返回該字符串。于是我們得到該錯誤信息:

          open?/test.txt:?No?such?file?or?directory

          結(jié)構(gòu)體 PathErrorPath 字段,就有導(dǎo)致錯誤的文件路徑。我們修改前面寫的程序,打印出該路徑。

          package?main

          import?(
          ????"fmt"
          ????"os"
          )

          func?main()?{
          ????f,?err?:=?os.Open("/test.txt")
          ????if?err,?ok?:=?err.(*os.PathError);?ok?{
          ????????fmt.Println("File?at?path",?err.Path,?"failed?to?open")
          ????????return
          ????}
          ????fmt.Println(f.Name(),?"opened?successfully")
          }

          在 playground 上運行[10]

          在上面的程序里,我們在第 10 行使用了類型斷言[11](Type Assertion)來獲取 error 接口的底層值(Underlying Value)。接下來在第 11 行,我們使用 err.Path 來打印該路徑。該程序會輸出:

          File?at?path?/test.txt?failed?to?open

          很棒!我們已經(jīng)使用類型斷言成功獲取到了該錯誤的文件路徑。

          2. 斷言底層結(jié)構(gòu)體類型,調(diào)用方法獲取更多信息

          第二種獲取更多錯誤信息的方法,也是對底層類型進行斷言,然后通過調(diào)用該結(jié)構(gòu)體類型的方法,來獲取更多的信息。

          我們通過一個實例來理解這一點。

          標準庫中的 DNSError 結(jié)構(gòu)體類型定義如下:

          type?DNSError?struct?{
          ????...
          }

          func?(e?*DNSError)?Error()?string?{
          ????...
          }
          func?(e?*DNSError)?Timeout()?bool?{
          ????...
          }
          func?(e?*DNSError)?Temporary()?bool?{
          ????...
          }

          從上述代碼可以看到,DNSError 結(jié)構(gòu)體還有 Timeout() boolTemporary() bool 兩個方法,它們返回一個布爾值,指出該錯誤是由超時引起的,還是臨時性錯誤。

          接下來我們編寫一個程序,斷言 *DNSError 類型,并調(diào)用這些方法來確定該錯誤是臨時性錯誤,還是由超時導(dǎo)致的。

          package?main

          import?(
          ????"fmt"
          ????"net"
          )

          func?main()?{
          ????addr,?err?:=?net.LookupHost("golangbot123.com")
          ????if?err,?ok?:=?err.(*net.DNSError);?ok?{
          ????????if?err.Timeout()?{
          ????????????fmt.Println("operation?timed?out")
          ????????}?else?if?err.Temporary()?{
          ????????????fmt.Println("temporary?error")
          ????????}?else?{
          ????????????fmt.Println("generic?error:?",?err)
          ????????}
          ????????return
          ????}
          ????fmt.Println(addr)
          }

          注:在 playground 無法進行 DNS 解析。請在你的本地運行該程序

          在上述程序中,我們在第 9 行,試圖獲取 golangbot123.com(無效的域名) 的 ip。在第 10 行,我們通過 *net.DNSError 的類型斷言,獲取到了錯誤的底層值。接下來的第 11 行和第 13 行,我們分別檢查了該錯誤是由超時引起的,還是一個臨時性錯誤。

          在本例中,我們的錯誤既不是臨時性錯誤,也不是由超時引起的,因此該程序輸出:

          generic?error:??lookup?golangbot123.com:?no?such?host

          如果該錯誤是臨時性錯誤,或是由超時引發(fā)的,那么對應(yīng)的 if 語句會執(zhí)行,于是我們就可以適當?shù)靥幚硭鼈儭?/p>

          3. 直接比較

          第三種獲取錯誤的更多信息的方式,是與 error 類型的變量直接比較。我們通過一個示例來理解。

          filepath 包中的 `Glob`[12] 用于返回滿足 glob 模式的所有文件名。如果模式寫的不對,該函數(shù)會返回一個錯誤 ErrBadPattern。

          filepath 包中的 ErrBadPattern 定義如下:

          var?ErrBadPattern?=?errors.New("syntax?error?in?pattern")

          errors.New() 用于創(chuàng)建一個新的錯誤。我們會在下一教程中詳細討論它。

          當模式不正確時,Glob 函數(shù)會返回 ErrBadPattern。

          我們來寫一個小程序來看看這個錯誤。

          package?main

          import?(
          ????"fmt"
          ????"path/filepath"
          )

          func?main()?{
          ????files,?error?:=?filepath.Glob("[")
          ????if?error?!=?nil?&&?error?==?filepath.ErrBadPattern?{
          ????????fmt.Println(error)
          ????????return
          ????}
          ????fmt.Println("matched?files",?files)
          }

          在 playground 上運行[13]

          在上述程序里,我們查詢了模式為 [ 的文件,然而這個模式寫的不正確。我們檢查了該錯誤是否為 nil。為了獲取該錯誤的更多信息,我們在第 10 行將 error 直接與 filepath.ErrBadPattern 相比較。如果該條件滿足,那么該錯誤就是由模式錯誤導(dǎo)致的。該程序會輸出:

          syntax?error?in?pattern

          標準庫在提供錯誤的詳細信息時,使用到了上述提到的三種方法。在下一教程里,我們會通過這些方法來創(chuàng)建我們自己的自定義錯誤。

          不可忽略錯誤

          絕不要忽略錯誤。忽視錯誤會帶來問題。接下來我重寫上面的示例,在列出所有滿足模式的文件名時,我省略了錯誤處理的代碼。

          package?main

          import?(
          ????"fmt"
          ????"path/filepath"
          )

          func?main()?{
          ????files,?_?:=?filepath.Glob("[")
          ????fmt.Println("matched?files",?files)
          }

          在 playground 上運行[14]

          我們已經(jīng)從前面的示例知道了這個模式是錯誤的。在第 9 行,通過使用 _ 空白標識符,我忽略了 Glob 函數(shù)返回的錯誤。我在第 10 行簡單打印了所有匹配的文件。該程序會輸出:

          matched?files?[]

          由于我忽略了錯誤,輸出看起來就像是沒有任何匹配了 glob 模式的文件,但實際上這是因為模式的寫法不對。所以絕不要忽略錯誤。

          本教程到此結(jié)束。

          這一教程我們討論了該如何處理程序中出現(xiàn)的錯誤,也討論了如何查詢關(guān)于錯誤的更多信息。簡單概括一下本教程討論的內(nèi)容:

          • 什么是錯誤?
          • 錯誤的表示
          • 獲取錯誤詳細信息的各種方法
          • 不能忽視錯誤

          在下一教程,我們會創(chuàng)建我們自己的自定義錯誤,并給標準錯誤增加更多的語境(Context)。

          祝你愉快。

          上一教程 - Defer

          下一教程 - 自定義錯誤


          via: https://golangbot.com/error-handling/

          作者:Nick Coghlan[15]譯者:Noluye[16]校對:polaris1119[17]

          本文由 GCTT[18] 原創(chuàng)編譯,Go 中文網(wǎng)[19] 榮譽推出

          參考資料

          [1]

          Golang 系列教程: https://studygolang.com/subject/2

          [2]

          在 playground 中運行: https://play.golang.org/p/yOhAviFM05

          [3]

          Open: https://golang.org/pkg/os/#Open

          [4]

          函數(shù): https://studygolang.com/articles/11892

          [5]

          方法: https://studygolang.com/articles/12264

          [6]

          接口: https://studygolang.com/articles/12266

          [7]

          Open: https://golang.org/pkg/os/#OpenFile

          [8]

          PathError: https://golang.org/pkg/os/#PathError

          [9]

          結(jié)構(gòu)體: https://studygolang.com/articles/12263

          [10]

          在 playground 上運行: https://play.golang.org/p/JQrqWU7Jf9

          [11]

          類型斷言: https://studygolang.com/articles/12266

          [12]

          Glob: https://golang.org/pkg/path/filepath/#Glob

          [13]

          在 playground 上運行: https://play.golang.org/p/zbVDDHnMZU

          [14]

          在 playground 上運行: https://play.golang.org/p/2k8r_Qg_lc

          [15]

          Nick Coghlan: https://golangbot.com/about/

          [16]

          Noluye: https://github.com/Noluye

          [17]

          polaris1119: https://github.com/polaris1119

          [18]

          GCTT: https://github.com/studygolang/GCTT

          [19]

          Go 中文網(wǎng): https://studygolang.com/



          推薦閱讀


          福利

          我為大家整理了一份從入門到進階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進階看什么。關(guān)注公眾號 「polarisxu」,回復(fù) ebook 獲取;還可以回復(fù)「進群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

          瀏覽 42
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  AAA黄色 | 一级黄色中文电影影视视屏 | 操我操综合| 在线看片操 | 日本视频一区二区三区四区 |