<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』Golang 中高效的錯(cuò)誤處理

          共 7114字,需瀏覽 15分鐘

           ·

          2022-02-21 20:31

          Go 中的錯(cuò)誤處理與其他主流編程語言如 Java、JavaScript 或 Python 有些不同。Go 的內(nèi)置錯(cuò)誤不包含堆棧痕跡,也不支持傳統(tǒng)的try/catch方法來處理它們。相反,Go 中的錯(cuò)誤只是由函數(shù)返回的值,它們的處理方式與其他數(shù)據(jù)類型基本相同 - 這帶來了令人驚嘆的輕量級(jí)和簡單設(shè)計(jì)。

          在這篇文章中,我將展示 Go 中處理錯(cuò)誤的基礎(chǔ)知識(shí),以及一些你可以在代碼中遵循的簡單策略,以確保你的程序健壯且易于調(diào)試。

          錯(cuò)誤類型

          Go 中的錯(cuò)誤類型是通過以下接口實(shí)現(xiàn)的:

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

          所以基本上,一個(gè)error是任何實(shí)現(xiàn)Error()方法的東西,它以字符串形式返回錯(cuò)誤信息。就是這么簡單!

          構(gòu)建錯(cuò)誤

          錯(cuò)誤可以使用 Go 的內(nèi)置 errors 包或 fmt 包來即時(shí)構(gòu)建。例如,下面的函數(shù)使用 errors 包來返回一個(gè)帶有靜態(tài)錯(cuò)誤信息的新錯(cuò)誤:

          package?main

          import?"errors"

          func?DoSomething()?error?{
          ????return?errors.New("something?didn't?work")
          }

          同樣地,fmt包可以用來給錯(cuò)誤添加動(dòng)態(tài)數(shù)據(jù),比如一個(gè)int、string或其他 error。例如:

          package?main

          import?"fmt"

          func?Divide(a,?b?int)?(int,?error)?{
          ????if?b?==?0?{
          ????????return?0,?fmt.Errorf("can't?divide?'%d'?by?zero",?a)
          ????}
          ????return?a?/?b,?nil
          }

          注意fmt.Errorf在用來包裝另一個(gè)%w格式動(dòng)詞的錯(cuò)誤時(shí)將被證明是非常有用的 - 我將在文章中進(jìn)一步詳細(xì)說明。

          在上述例子中,還有一些重要的事情需要注意。

          • error可以以nil的形式返回,事實(shí)上,它是 Go 中error的默認(rèn)值,或者說 “零值”。這一點(diǎn)很重要,因?yàn)闄z查if err != nil是確定是否遇到錯(cuò)誤的習(xí)慣用法(取代你在其他編程語言中可能熟悉的try/catch語句)。
          • error通常作為一個(gè)函數(shù)的最后一個(gè)參數(shù)返回。因此在我們上面的例子中,我們依次返回一個(gè)int和一個(gè)error
          • 當(dāng)我們確實(shí)返回一個(gè)error時(shí),函數(shù)返回的其他參數(shù)通常會(huì)以其默認(rèn)的 零值 返回。一個(gè)函數(shù)的用戶可能期望,如果返回一個(gè)非零值的error,那么返回的其他參數(shù)就沒有意義了。
          • 最后,錯(cuò)誤信息通常以小寫字母書寫,不以標(biāo)點(diǎn)符號(hào)結(jié)束。但也有例外,例如,當(dāng)包括一個(gè)專有名詞、一個(gè)以大寫字母開頭的函數(shù)名稱等。
          定義預(yù)期的錯(cuò)誤

          Go 中另一個(gè)重要的技術(shù)是定義預(yù)期錯(cuò)誤,這樣就可以在代碼的其他部分明確地檢查這些錯(cuò)誤。當(dāng)你需要在遇到某種錯(cuò)誤時(shí)執(zhí)行不同的代碼分支時(shí),這就非常有用。

          定義哨兵錯(cuò)誤

          在前面的Divide函數(shù)的基礎(chǔ)上,我們可以通過預(yù)先定義一個(gè) “哨兵” 錯(cuò)誤改進(jìn)錯(cuò)誤提示。調(diào)用函數(shù)可以使用errors.Is明確地檢查這個(gè)error

          package?main

          import?(
          ????"errors"
          ????"fmt"
          )

          var?ErrDivideByZero?=?errors.New("divide?by?zero")

          func?Divide(a,?b?int)?(int,?error)?{
          ????if?b?==?0?{
          ????????return?0,?ErrDivideByZero
          ????}
          ????return?a?/?b,?nil
          }

          func?main()?{
          ????a,?b?:=?10,?0
          ????result,?err?:=?Divide(a,?b)
          ????if?err?!=?nil?{
          ????????switch?{
          ????????case?errors.Is(err,?ErrDivideByZero):
          ????????????fmt.Println("divide?by?zero?error")
          ????????default:
          ????????????fmt.Printf("unexpected?division?error:?%s\n",?err)
          ????????}
          ????????return
          ????}

          ????fmt.Printf("%d?/?%d?=?%d\n",?a,?b,?result)
          }
          定義自定義錯(cuò)誤類型

          大多數(shù)錯(cuò)誤處理的用例都可以用上面的策略來覆蓋,然而,有時(shí)你可能需要一些額外的功能。也許你想讓一個(gè)error攜帶額外的數(shù)據(jù)字段,或者當(dāng)錯(cuò)誤信息被打印出來時(shí),能用動(dòng)態(tài)值來填充自己。

          你可以在 Go 中通過實(shí)現(xiàn)自定義錯(cuò)誤類型來做到這一點(diǎn)。

          下面是對(duì)以前的例子的輕微改造。注意新的類型DivisionError,它實(shí)現(xiàn)了Error接口。我們可以利用errors.As來檢查并從標(biāo)準(zhǔn)error轉(zhuǎn)換到我們更具體的DivisionError。

          package?main

          import?(
          ????"errors"
          ????"fmt"
          )

          type?DivisionError?struct?{
          ????IntA?int
          ????IntB?int
          ????Msg??string
          }

          func?(e?*DivisionError)?Error()?string?{?
          ????return?e.Msg
          }

          func?Divide(a,?b?int)?(int,?error)?{
          ????if?b?==?0?{
          ????????return?0,?&DivisionError{
          ????????????Msg:?fmt.Sprintf("cannot?divide?'%d'?by?zero",?a),
          ????????????IntA:?a,?IntB:?b,
          ????????}
          ????}
          ????return?a?/?b,?nil
          }

          func?main()?{
          ????a,?b?:=?10,?0
          ????result,?err?:=?Divide(a,?b)
          ????if?err?!=?nil?{
          ????????var?divErr?*DivisionError
          ????????switch?{
          ????????case?errors.As(err,?&divErr):
          ????????????fmt.Printf("%d?/?%d?is?not?mathematically?valid:?%s\n",
          ??????????????divErr.IntA,?divErr.IntB,?divErr.Error())
          ????????default:
          ????????????fmt.Printf("unexpected?division?error:?%s\n",?err)
          ????????}
          ????????return
          ????}

          ????fmt.Printf("%d?/?%d?=?%d\n",?a,?b,?result)
          }

          注意:必要時(shí),你也可以自定義errors.Is和errors.As的行為。請(qǐng)看這個(gè) Go.dev 博客的一個(gè)例子。

          另一個(gè)說明:errors.Is是在 Go 1.13 中添加的,比檢查err == ...更可取。下面有更多關(guān)于這個(gè)問題的內(nèi)容。

          包裝錯(cuò)誤

          在迄今為止的這些例子中,error都是通過一個(gè)函數(shù)調(diào)用來創(chuàng)建、返回和處理的。換句話說,參與 “冒泡” error的函數(shù)堆棧只有一層深度。

          通常在現(xiàn)實(shí)世界的程序中,可能會(huì)有更多的函數(shù)參與其中 – 從產(chǎn)生error的函數(shù),到最終處理error的地方,以及中間的任何數(shù)量的附加函數(shù)。

          在 Go 1.13 中,引入了幾個(gè)新的errors API,包括errors.Wraperrors.Unwrap,它們?cè)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">error “冒泡” 時(shí)對(duì)其應(yīng)用額外的上下文,以及檢查特定的error類型,不管一個(gè)error被包裹了多少次。

          一段歷史: 在 2019 年 Go 1.13 發(fā)布之前,標(biāo)準(zhǔn)庫并不包含很多處理錯(cuò)誤的 API–基本上只有errors.New和fmt.Errorf。因此,你可能會(huì)在別的包里遇到?jīng)]有實(shí)現(xiàn)一些較新錯(cuò)誤 API 的遺留 Go 程序。許多遺留程序也使用第三方錯(cuò)誤庫,如 pkg/errors。最后,正式提案在 2018 年被記錄下來,其中提出了許多我們今天在 Go 1.13+ 中看到的功能。

          舊的方式(Go 1.13 之前)

          通過看一些舊的 API 有局限性的例子,對(duì)比一下就很容易看出 Go 1.13+ 中新的錯(cuò)誤 API 多么有用。

          讓我們考慮一個(gè)管理用戶數(shù)據(jù)庫的簡單程序。在這個(gè)程序中,我們將有幾個(gè)函數(shù)參與到數(shù)據(jù)庫錯(cuò)誤的生命周期中。

          為了簡單起見,讓我們用一個(gè)完全 “假” 的數(shù)據(jù)庫來代替真正的數(shù)據(jù)庫,我們從"example.com/fake/users/db"導(dǎo)入。

          我們還假設(shè)這個(gè)假數(shù)據(jù)庫已經(jīng)包含了一些查找和更新用戶記錄的功能。而且,用戶記錄被定義為一個(gè)結(jié)構(gòu)體,看起來像:

          package?db

          type?User?struct?{
          ??ID???????string
          ??Username?string
          ??Age??????int
          }

          func?FindUser(username?string)?(*User,?error)?{?/*?...?*/?}
          func?SetUserAge(user?*User,?age?int)?error?{?/*?...?*/?}

          下面是我們的示例程序:

          package?main

          import?(
          ????"errors"
          ????"fmt"

          ????"example.com/fake/users/db"
          )

          func?FindUser(username?string)?(*db.User,?error)?{
          ????return?db.Find(username)
          }

          func?SetUserAge(u?*db.User,?age?int)?error?{
          ????return?db.SetAge(u,?age)
          }

          func?FindAndSetUserAge(username?string,?age?int)?error?{
          ??var?user?*User
          ??var?err?error

          ??user,?err?=?FindUser(username)
          ??if?err?!=?nil?{
          ??????return?err
          ??}

          ??if?err?=?SetUserAge(user,?age);?err?!=?nil?{
          ??????return?err
          ??}

          ??return?nil
          }

          func?main()?{
          ????if?err?:=?FindAndSetUserAge("[email protected]",?21);?err?!=?nil?{
          ????????fmt.Println("failed?finding?or?updating?user:?%s",?err)
          ????????return
          ????}

          ????fmt.Println("successfully?updated?user's?age")
          }

          現(xiàn)在,如果我們的一個(gè)數(shù)據(jù)庫操作因?yàn)橐恍?malformed request (錯(cuò)誤的請(qǐng)求) 而失敗,會(huì)發(fā)生什么?

          main函數(shù)中的錯(cuò)誤檢查應(yīng)該捕獲它,并打印出類似這樣的東西:

          failed?finding?or?updating?user:?malformed?request

          但這兩個(gè)數(shù)據(jù)庫操作中的哪一個(gè)產(chǎn)生了錯(cuò)誤?不幸的是,我們的錯(cuò)誤日志中沒有足夠的信息來知道它是來自FindUser還是SetUserAge。

          Go 1.13 增加了一個(gè)簡單的方法來添加這些信息。

          錯(cuò)誤更好地被包裝起來

          下面的代碼段經(jīng)過重構(gòu),使用fmt.Errorf%w動(dòng)詞來 “包裝” 錯(cuò)誤,因?yàn)樗鼈兺ㄟ^其他函數(shù)調(diào)用 “冒泡” 了。這增加了所需的上下文,從而有可能推斷出在前面的例子中哪些數(shù)據(jù)庫操作失敗了。

          package?main

          import?(
          ????"errors"
          ????"fmt"

          ????"example.com/fake/users/db"
          )

          func?FindUser(username?string)?(*db.User,?error)?{
          ????u,?err?:=?db.Find(username)
          ????if?err?!=?nil?{
          ????????return?nil,?fmt.Errorf("FindUser:?failed?executing?db?query:?%w",?err)
          ????}
          ????return?u,?nil
          }

          func?SetUserAge(u?*db.User,?age?int)?error?{
          ????if?err?:=?db.SetAge(u,?age);?err?!=?nil?{
          ??????return?fmt.Errorf("SetUserAge:?failed?executing?db?update:?%w",?err)
          ????}
          }

          func?FindAndSetUserAge(username?string,?age?int)?error?{
          ??var?user?*User
          ??var?err?error

          ??user,?err?=?FindUser(username)
          ??if?err?!=?nil?{
          ??????return?fmt.Errorf("FindAndSetUserAge:?%w",?err)
          ??}

          ??if?err?=?SetUserAge(user,?age);?err?!=?nil?{
          ??????return?fmt.Errorf("FindAndSetUserAge:?%w",?err)
          ??}

          ??return?nil
          }

          func?main()?{
          ????if?err?:=?FindAndSetUserAge("[email protected]",?21);?err?!=?nil?{
          ????????fmt.Println("failed?finding?or?updating?user:?%s",?err)
          ????????return
          ????}

          ????fmt.Println("successfully?updated?user's?age")
          }

          如果我們重新運(yùn)行程序并遇到同樣的錯(cuò)誤,日志應(yīng)該打印如下:

          failed?finding?or?updating?user:?FindAndSetUserAge:?SetUserAge:?failed?executing?db?update:?malformed?request

          現(xiàn)在我們的錯(cuò)誤消息包含了足夠的信息,我們可以看到問題起源于db.SetUserAge函數(shù)。咻!這無疑為我們節(jié)省了一些調(diào)試的時(shí)間!

          如果使用得當(dāng),錯(cuò)誤包裝可以提供關(guān)于錯(cuò)誤脈絡(luò)的額外內(nèi)容,其方式類似于傳統(tǒng)的堆棧跟蹤。

          包裝也保留了原始錯(cuò)誤,這意味著errors.Iserrors.As同樣有效,不管一個(gè)錯(cuò)誤被封裝了多少次。我們還可以調(diào)用errors.Unwrap來返回錯(cuò)誤鏈中的前一個(gè)錯(cuò)誤。

          好奇地想知道錯(cuò)誤包裝是如何工作的?看看 fmt.Errorf, %w 動(dòng)詞 和 錯(cuò)誤 API的內(nèi)部細(xì)節(jié)吧。

          何時(shí)包裝

          一般來說,在每次 “冒泡” 時(shí),即每次從一個(gè)函數(shù)中收到錯(cuò)誤并想繼續(xù)將其返回到函數(shù)鏈中時(shí),至少用函數(shù)的名稱來包裹錯(cuò)誤是個(gè)好主意。

          Wrapping an error adds the gift of context 然而,也有一些例外情況,在這種情況下,包裝錯(cuò)誤可能是不合適的。

          由于包裝錯(cuò)誤總是保留原始的錯(cuò)誤信息,有時(shí)暴露這些潛在的問題可能是一個(gè)安全、隱私,甚至是用戶體驗(yàn)的問題。在這種情況下,可能值得處理錯(cuò)誤并返回一個(gè)新的錯(cuò)誤,而不是包裝它。如果你正在編寫一個(gè)開源庫或 REST API,不希望將底層錯(cuò)誤信息返回給第三方用戶,就可能是這種情況。

          結(jié)論

          這是個(gè)總結(jié)!綜上所述,這就是這里所涉及的主要內(nèi)容:- Go 中的錯(cuò)誤只是一些輕量級(jí)的值,實(shí)現(xiàn)了Errorinterface - 預(yù)定義的錯(cuò)誤將改善信號(hào),允許我們檢查是哪個(gè)錯(cuò)誤發(fā)生了 - 包裝錯(cuò)誤以增加足夠的上下文來跟蹤函數(shù)調(diào)用(類似于堆棧跟蹤)

          我希望你覺得這個(gè)有效的錯(cuò)誤處理指南很有用。如果你想了解更多,我附上了一些相關(guān)的文章,這些文章是我在 Go 中進(jìn)行強(qiáng)大錯(cuò)誤處理的過程中發(fā)現(xiàn)的。

          參考文獻(xiàn)

          • Error handling and Go
          • Go 1.13 Errors
          • Go Error Doc
          • Go By Example: Errors
          • Go By Example: Panic


          原文信息

          原文地址:https://earthly.dev/blog/golang-errors/

          原文作者:Brandon Schurman

          本文永久鏈接:https://github.com/gocn/translator/blob/master/2022/w06_effective_error_handling_in_golang.md

          譯者:Cluas



          想要了解關(guān)于 Go 的更多資訊,還可以通過掃描的方式,進(jìn)群一起探討哦~




          瀏覽 43
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  看一级一级黄色免费视频 | 麻豆传剧原创在线观看 | 亚洲精品成人7777777 | 国产美女操逼黄站 | 国产激情乱伦 |