- 原文地址:peter.bourgon.org/blog/2019/0…
- 原文作者:Peter
- 譯文地址:github.com/watermelo/d…
- 譯者:咔嘰咔嘰
- 譯者水平有限,如有翻譯或理解謬誤,煩請幫忙指出
Go 1.13 引入了一個增強的package errors,大致標準化了錯誤處理。就個人而言,我覺得它的 API 令人有點困惑。本文提供一些如何更有效使用它的參考。
建立 errors
sentinel errors(譯者注:表示在此錯誤中斷,程式不會繼續往下處理)和以前一樣。將它們命名為 ErrXxx,使用 errors.New 來建立它們。
var ErrFoo = errors.New("foo error")
複製程式碼
錯誤型別基本上也和以前一樣。將它們命名為 XxxError,並確保它們具有 Error 方法,以滿足 error 介面。
type BarError struct {
Reason string
}
func (e BarError) Error() string {
return fmt.Sprintf("bar error: %s", e.Reason)
}
複製程式碼
如果你的錯誤型別包裝了另一個錯誤,就需要提供 Unwrap 方法。
type BazError struct {
Reason string
Inner error
}
func (e BazError) Error() string {
if e.Inner != nil {
return fmt.Sprintf("baz error: %s: %v", e.Reason, e.Inner)
}
return fmt.Sprintf("baz error: %s", e.Reason)
}
func (e BazError) Unwrap() error {
return e.Inner
}
複製程式碼
包裝和返回錯誤
預設情況下,當你在函式中遇到錯誤並需要將其返回給呼叫者時,可以通過 fmt.Errorf 的 %w
格式,使用相關上下文包裝錯誤。
func process(j Job) error {
result, err := preprocess(j)
if err != nil {
return fmt.Errorf("error preprocessing job: %w", err)
}
複製程式碼
此過程稱為錯誤註解。需要避免返回未註解的錯誤,因為這可能使呼叫者不知道出錯的地方在哪裡。
另外,考慮通過自定義錯誤型別(如上面的 BazError)包裝錯誤,以獲得更復雜的用例。
p := getPriority()
widget, err := manufacture(p, result)
if err != nil {
return ManufacturingError{Priority: p, Error: err}
}
複製程式碼
錯誤檢查
大多數情況下,當你收到錯誤時,不需要關心細節。如果你的程式碼執行失敗了,你需要報出錯誤(例如記錄它)並繼續;或者,如果無法繼續,可以使用上下文來註解錯誤,並將其返回給呼叫者。
如果你想知道收到的是哪個錯誤,可以用 errors.Is 檢查 sentinel errors,也可以用 errors.As來檢查錯誤值。
err := f()
if errors.Is(err, ErrFoo) {
// you know you got an ErrFoo
// respond appropriately
}
var bar BarError
if errors.As(err, &bar) {
// you know you got a BarError
// bar's fields are populated
// respond appropriately
}
複製程式碼
errors.Is 和 errors.As 會嘗試以遞迴的方式解包錯誤來找到匹配項。此程式碼演示了基本的錯誤包裝和檢查技術(譯者注:需要科學上網,把這段程式碼貼到文章末尾了)。檢視 func a()
中檢查的順序,然後嘗試更改 func c()
返回的錯誤,以獲得關於執行的流程。
正如文件所述,更偏向使用 errors.Is 來檢查普通等式,例如 if err == ErrFoo
;更偏向使用 errors.As 來斷言普通型別,例如 if e,ok := err.(MyError)
,因為普通版本不執行 unwrap 操作。如果你明確不希望呼叫者 unwrap 錯誤,可以為 fmt.Errorf
提供不同的格式化動詞,例如 %v
;或者不要在錯誤型別上提供 Unwrap
方法。但這些例不是常見的。
示例
package main
import (
"errors"
"fmt"
"log"
)
func main() {
i, err := a()
log.Printf("i=%d err=%v", i, err)
}
//
//
//
func a() (int, error) {
i, err := b()
if errors.Is(err, ErrFoo) {
return 0, fmt.Errorf("tragedy: %w", err)
}
var bar BarError
if errors.As(err, &bar) {
return 0, fmt.Errorf("comedy: %w", err)
}
var baz BazError
if errors.As(err, &baz) {
return 0, fmt.Errorf("farce: %w", err)
}
return i, nil
}
func b() (int, error) {
if err := c(); err != nil {
return 0, fmt.Errorf("error executing c: %w", err)
}
return 1, nil
}
func c() error {
// return ErrFoo
// return BarError{Reason: "?"}
// return BazError{Reason: "☹️"}
return BazError{Reason: "?", Inner: ErrFoo}
}
//
//
//
var ErrFoo = errors.New("foo error")
//
//
//
type BarError struct {
Reason string
}
func (e BarError) Error() string {
return fmt.Sprintf("bar error: %s", e.Reason)
}
//
//
//
type BazError struct {
Reason string
Inner error
}
func (e BazError) Unwrap() error {
fmt.Println("fuck")
return e.Inner
}
func (e BazError) Error() string {
if e.Inner != nil {
return fmt.Sprintf("baz error: %s: %v", e.Reason, e.Inner)
}
return fmt.Sprintf("baz error: %s", e.Reason)
}
複製程式碼