成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

關于Golang錯誤處理的一些思考

開發 前端
如果你還沒在 error 上栽跟頭,那么當你栽了跟頭時才會哭著想起來,當年為什么沒好好思考和反省錯誤處理這么一個宏大的話題

寫在前面:如果你還沒在 error 上栽跟頭,那么當你栽了跟頭時才會哭著想起來,當年為什么沒好好思考和反省錯誤處理這么一個宏大的話題

關于 Golang 錯誤處理的實踐

Golang 有很多優點,這也是它如此流行的主要原因。但是 Go 1 對錯誤處理的支持過于簡單了,以至于日常開發中會有諸多不便利,遭到很多開發者的吐槽。這些不足催生了一些開源解決方案。與此同時, Go 官方也在從語言和標準庫層面作出改進。這篇文章將給出幾種常見創建錯誤的方式并分析一些常見問題,對比各種解決方案,并展示了迄今為止(go 1.13)的最佳實踐。

[[338649]]

幾種創建錯誤的方式

首先介紹幾種常見的創建錯誤的方法

基于字符串的錯誤

  1. err1 := errors.New("math: square root of negative number"
  2. err2 := fmt.Errorf("math: square root of negative number %g", x) 

帶有數據的自定義錯誤

  1. package serr 
  2.  
  3. import ( 
  4.   "fmt" 
  5.   "github.com/satori/go.uuid" 
  6.   "log" 
  7.   "runtime/debug" 
  8.   "time" 
  9. // 自定義基礎錯誤類型 
  10. type BaseError struct { 
  11.   InnerError error 
  12.   Message    string 
  13.   StackTrace string 
  14.   Misc       map[string]interface{} 
  15.  
  16. func WrapError(err error, message string, messageArgs ...interface{}) BaseError { 
  17.   return BaseError{ 
  18.     InnerError: err, 
  19.     Message:    fmt.Sprintf(message, messageArgs), 
  20.     StackTrace: string(debug.Stack()), 
  21.     Misc:       make(map[string]interface{}), 
  22.   } 
  23.  
  24. func (err *BaseError) Error() string { 
  25. // 實現 Error 接口 
  26.   return err.Message 
  27.  
  28. // 具體使用 
  29. // "intermediate" module 
  30. type IntermediateErr struct { 
  31.   error 
  32.  
  33. func runJob(id string) error { 
  34.   const jobBinPath = "/bad/job/binary" 
  35.   isExecutable, err := isGloballyExec(jobBinPath) 
  36.   iferr != nil{ 
  37.     return IntermediateErr{wrapError( err, 
  38.     "cannot run job %q: requisite binaries not available"
  39.     id, )} 
  40.   } else if isExecutable == false { 
  41.     return wrapError( 
  42.       nil, 
  43.       "cannot run job %q: requisite binaries are not executable", id, 
  44.     ) 
  45.   } 
  46.   return exec.Command(jobBinPath, "--id="+id).Run() 
  47. }  
  48.    

拋出問題

開發中經常需要檢查返回的錯誤值并作相應處理。下面給出一個最簡單的示例。

  1. import ( 
  2.    "database/sql" 
  3.    "fmt" 
  4.  
  5. func GetSql() error { 
  6.    return sql.ErrNoRows 
  7.  
  8. func Call() error { 
  9.    return GetSql() 
  10.  
  11. func main() { 
  12.    err := Call() 
  13.    if err != nil { 
  14.       fmt.Printf("got err, %+v\n", err) 
  15.    } 
  16. //Outputs: 
  17. // got err, sql: no rows in result set 

有時需要根據返回的 error 類型作不同處理,例如:

  1. import ( 
  2.    "database/sql" 
  3.    "fmt" 
  4.  
  5. func GetSql() error { 
  6.    return sql.ErrNoRows 
  7.  
  8. func Call() error { 
  9.    return GetSql() 
  10.  
  11. func main() { 
  12.    err := Call() 
  13.    if err == sql.ErrNoRows { 
  14.       fmt.Printf("data not found, %+v\n", err) 
  15.       return 
  16.    } 
  17.    if err != nil { 
  18.       // Unknown error 
  19.    } 
  20. //Outputs: 
  21. // data not found, sql: no rows in result set 

實踐中經常需要為錯誤增加上下文信息后再返回,以方便調用者了解錯誤場景。例如 Getcall 方法時常寫成:

  1. func Getcall() error { 
  2.    return fmt.Errorf("GetSql err, %v", sql.ErrNoRows) 

不過這個時候 err==sql.ErrNoRows 就不成立了。除此之外,上述寫法都在返回錯誤時都丟掉了調用棧這個重要的信息。我們需要更靈活、更通用的方式來應對此類問題。

解決方案

針對存在的不足,目前有幾種解決方案。這些方式可以對錯誤進行上下文包裝,并攜帶原始錯誤信息, 還能盡量保留完整的調用棧

方案 1:github.com/pkg/errors

如果只有錯誤的文本,我們很難定位到具體的出錯地點。雖然通過在代碼中搜索錯誤文本也是有可能找到出錯地點的,但是信息有限。所以,在實踐中,我們往往會將出錯時的調用棧信息也附加上去。調用棧對消費方是沒有意義的,從隔離和自治的角度來看,消費方唯一需要關心的就是錯誤文本和錯誤類型。調用棧對實現者自身才是是有價值的。所以,如果一個方法需要返回錯誤,我們一般會使用 errors.WithStack(err) 或者 errors.Wrap(err,"custom message") 的方式,把此刻的調用棧加到error里去,并且在某個統一地方記錄日志,方便開發者快速定位問題。

  1. Wrap 方法用來包裝底層錯誤,增加上下文文本信息并附加調用棧。一般用于包裝對第三方代碼(標準庫或第三方庫)的調用。
  2. WithMessage 方法僅增加上下文文本信息,不附加調用棧。如果確定錯誤已被 Wrap 過或不關心調用棧,可以使用此方法。注意:不要反復 Wrap ,會導致調用棧重復
  3. Cause 方法用來判斷底層錯誤 。

現在我們用這三個方法來重寫上面的代碼:

  1. import ( 
  2.    "database/sql" 
  3.    "fmt" 
  4.  
  5.    "github.com/pkg/errors" 
  6.  
  7. func GetSql() error { 
  8.    return errors.Wrap(sql.ErrNoRows, "GetSql failed"
  9.  
  10. func Call() error { 
  11.    return errors.WithMessage(GetSql(), "bar failed"
  12.  
  13. func main() { 
  14.    err := Call() 
  15.    if errors.Cause(err) == sql.ErrNoRows { 
  16.       fmt.Printf("data not found, %v\n", err) 
  17.       fmt.Printf("%+v\n", err) 
  18.       return 
  19.    } 
  20.    if err != nil { 
  21.       // unknown error 
  22.    } 
  23. /*Output
  24. data not found, Call failed: GetSql failed: sql: no rows in result set 
  25. sql: no rows in result set 
  26. main.GetSql 
  27.     /usr/three/main.go:11 
  28. main.Call 
  29.     /usr/three/main.go:15 
  30. main.main 
  31.     /usr/three/main.go:19 
  32. runtime.main 
  33.     ... 
  34. */ 

從輸出內容可以看到, 使用 %v 作為格式化參數,那么錯誤信息會保持一行, 其中依次包含調用棧的上下文文本。使用 %+v ,則會輸出完整的調用棧詳情。如果不需要增加額外上下文信息,僅附加調用棧后返回,可以使用 WithStack 方法:

  1. func GetSql() error { 
  2.    return errors.WithStack(sql.ErrNoRows) 

注意:無論是 Wrap , WithMessage 還是 WithStack ,當傳入的 err 參數為 nil 時, 都會返回nil, 這意味著我們在調用此方法之前無需作 nil 判斷,保持了代碼簡潔

方案 2:golang.org/x/xerrors

結合社區反饋,Go 團隊開始考慮在 Go 2 中簡化錯誤處理的提案。Go 核心團隊成員 Russ Cox 在xerrors中部分實現了提案中的內容。它用與 github.com/pkg/errors 相似的思路解決同一問題, 引入了一個新的 fmt 格式化動詞: %w,使用 Is 進行判斷。

  1. import ( 
  2.    "database/sql" 
  3.    "fmt" 
  4.  
  5.    "golang.org/x/xerrors" 
  6.  
  7. func Call() error { 
  8.    if err := GetSql(); err != nil { 
  9.       return xerrors.Errorf("bar failed: %w", GetSql()) 
  10.    } 
  11.    return nil 
  12.  
  13. func GetSql() error { 
  14.    return xerrors.Errorf("GetSql failed: %w", sql.ErrNoRows) 
  15.  
  16. func main() { 
  17.    err := Call() 
  18.    if xerrors.Is(err, sql.ErrNoRows) { 
  19.       fmt.Printf("data not found, %v\n", err) 
  20.       fmt.Printf("%+v\n", err) 
  21.       return 
  22.    } 
  23.    if err != nil { 
  24.       // unknown error 
  25.    } 
  26. /* Outputs: 
  27. data not found, Call failed: GetSql failed: sql: no rows in result set 
  28. bar failed: 
  29.     main.Call 
  30.         /usr/four/main.go:12 
  31.   - GetSql failed: 
  32.     main.GetSql 
  33.         /usr/four/main.go:18 
  34.   - sql: no rows in result set 
  35. */ 

與 github.com/pkg/errors 相比,它有幾點不足:

  • 使用 : %w 代替了 Wrap , 看似簡化, 但失去了編譯期檢查。如果沒有冒號,或 : %w 不位于于格式化字符串的結尾,或冒號與百分號之間沒有空格,包裝將失效且不報錯;
  • 而且,調用 xerrors.Errorf 之前需要對參數進行nil判斷。這完全沒有簡化開發者的工作

方案 3:Go 1.13 內置支持

Go 1.13 將 xerrors 的部分功能(不是全部)整合進了標準庫。它繼承了上面提到的 xerrors 的全部缺點, 并額外貢獻了一項。因此目前沒有使用它的必要。

  1. import ( 
  2.    "database/sql" 
  3.    "errors" 
  4.    "fmt" 
  5.  
  6. func Call() error { 
  7.    if err := GetSql(); err != nil { 
  8.       return fmt.Errorf("Call failed: %w", GetSql()) 
  9.    } 
  10.    return nil 
  11.  
  12. func GetSql() error { 
  13.    return fmt.Errorf("GetSql failed: %w", sql.ErrNoRows) 
  14.  
  15. func main() { 
  16.    err := Call() 
  17.    if errors.Is(err, sql.ErrNoRows) { 
  18.       fmt.Printf("data not found,  %+v\n", err) 
  19.       return 
  20.    } 
  21.    if err != nil { 
  22.       // unknown error 
  23.    } 
  24. /* Outputs: 
  25. data not found,  Call failed: GetSql failed: sql: no rows in result set 
  26. */ 

上面的代碼與 xerrors 版本非常接近。但是它不支持調用棧信息輸出, 根據官方的說法, 此功能沒有明確的支持時間。因此其實用性遠低于 github.com/pkg/errors。

Golang 中將來可能的錯誤處理方式

在 Go2 的草案中,我們看到了有關于 error 相關的一些提案,那就是 check/handle 函數。

我們也許在下一個大版本的 Golang 可以像下面這樣處理錯誤:

  1. import "fmt" 
  2. func game() error { 
  3.     handle err { 
  4.         return fmt.Errorf("dependencies error: %v", err) 
  5.     } 
  6.  
  7.     resource := check findResource() // return resource, error 
  8.     defer func() { 
  9.         resource.Release() 
  10.     }() 
  11.  
  12.     profile := check loadProfile() // return profile, error 
  13.     defer func() { 
  14.         profile.Close() 
  15.     } 
  16.  
  17.     // ... 

感興趣的同學可以關注下這個提案:https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md

得出結論

  • 重要的是要記住,包裝錯誤會使該錯誤成為 API 的一部分。如果您不想將來將錯誤作為 API 的一部分來支持,則不應包裝該錯誤。無論是否包裝錯誤,錯誤文本都將相同。那些試圖理解錯誤的人將得到相同的信息,無論采用哪種方式; 是否要包裝錯誤的選擇取決于是否要給程序提供更多信息,以便他們可以做出更明智的決策,還是保留該信息以保留抽象層。

通過以上對比, 相信你已經有了選擇。再明確一下我的看法,如果你正在使用 github.com/pkg/errors ,那就保持現狀吧。目前還沒有比它更好的選擇。如果你已經大量使用 golang.org/x/xerrors , 別盲目換成 go 1.13 的內置方案。

總的來說,Go 在誕生之初就在各個方面表現得相當成熟、穩健。在演進路線上很少出現猶疑和搖擺, 而在錯誤處理方面卻是個例外。除了被廣泛吐槽的 if err != nil 之外, 就連其改進路線也備受爭議、分歧明顯,以致于一個改進提案都會因為壓倒性的反對意見而不得不作出調整。好在 Go 團隊比以前更加樂于傾聽社區意見,團隊甚至專門就此問題建了個反饋收集頁面。相信最終大家會找到更好的解決方案。

 

責任編輯:未麗燕 來源: Go Official Blog
相關推薦

2021-09-27 10:04:03

Go程序處理

2021-09-27 15:33:48

Go 開發技術

2017-12-21 07:54:07

2021-06-10 10:02:19

優化緩存性能

2023-10-28 16:30:19

Golang開發

2024-12-27 10:51:53

2012-12-19 09:36:49

測試自動化測試

2009-06-25 09:50:32

JSF

2020-02-03 16:03:36

疫情思考

2023-10-26 12:05:14

Golang開發

2015-10-12 08:59:57

異步代碼測試

2021-06-10 20:17:04

云網融合超融合

2021-08-08 10:44:33

安卓系統開發者手機廠商

2021-06-15 07:10:14

JavaScript異步編程

2018-06-29 14:51:41

Java健壯性實踐

2025-03-18 09:20:00

Go語言Golang

2021-09-28 06:28:51

EF錯誤用法

2011-11-30 15:57:18

2022-05-06 08:00:51

Golang編程語言Java

2011-01-19 10:50:31

軟件設計師
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 美女黄色在线观看 | 一区二区不卡高清 | 希岛爱理在线 | 国产精品欧美一区二区 | 五月婷婷激情网 | 日本a视频 | 91新视频 | 久久亚洲美女 | 欧美一级三级在线观看 | 粉嫩一区二区三区性色av | 91视频在线观看免费 | 99精品观看| 欧美一级大片免费看 | 九九综合 | 日韩精品一区二区三区中文字幕 | 羞羞色在线观看 | 国产婷婷精品 | 国产精品日日做人人爱 | 欧美一区二区三区在线 | 国产精品国产a级 | 国产精品福利在线 | 最新中文字幕第一页视频 | 特级丰满少妇一级aaaa爱毛片 | 美女视频.| 九九热在线视频 | 午夜伊人 | 在线a视频网站 | 在线观看av网站永久 | 成人午夜免费网站 | 国产一区二区三区在线视频 | 免费成人国产 | 国产高清一区二区三区 | 美女爽到呻吟久久久久 | 成人免费视频播放 | 欧美一级高清片 | 91麻豆精品国产91久久久更新资源速度超快 | 精品久久久久久久人人人人传媒 | 国产精品永久 | 国产激情三区 | 中文字幕精品一区二区三区精品 | 天堂一区二区三区 |