自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

關(guān)于Golang錯(cuò)誤處理的一些思考

開發(fā) 前端
如果你還沒在 error 上栽跟頭,那么當(dāng)你栽了跟頭時(shí)才會(huì)哭著想起來,當(dāng)年為什么沒好好思考和反省錯(cuò)誤處理這么一個(gè)宏大的話題

寫在前面:如果你還沒在 error 上栽跟頭,那么當(dāng)你栽了跟頭時(shí)才會(huì)哭著想起來,當(dāng)年為什么沒好好思考和反省錯(cuò)誤處理這么一個(gè)宏大的話題

關(guān)于 Golang 錯(cuò)誤處理的實(shí)踐

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

[[338649]]

幾種創(chuàng)建錯(cuò)誤的方式

首先介紹幾種常見的創(chuàng)建錯(cuò)誤的方法

基于字符串的錯(cuò)誤

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

帶有數(shù)據(jù)的自定義錯(cuò)誤

  1. package serr 
  2.  
  3. import ( 
  4.   "fmt" 
  5.   "github.com/satori/go.uuid" 
  6.   "log" 
  7.   "runtime/debug" 
  8.   "time" 
  9. // 自定義基礎(chǔ)錯(cuò)誤類型 
  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. // 實(shí)現(xiàn) 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.    

拋出問題

開發(fā)中經(jīng)常需要檢查返回的錯(cuò)誤值并作相應(yīng)處理。下面給出一個(gè)最簡(jiǎn)單的示例。

  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 

有時(shí)需要根據(jù)返回的 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 

實(shí)踐中經(jīng)常需要為錯(cuò)誤增加上下文信息后再返回,以方便調(diào)用者了解錯(cuò)誤場(chǎng)景。例如 Getcall 方法時(shí)常寫成:

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

不過這個(gè)時(shí)候 err==sql.ErrNoRows 就不成立了。除此之外,上述寫法都在返回錯(cuò)誤時(shí)都丟掉了調(diào)用棧這個(gè)重要的信息。我們需要更靈活、更通用的方式來應(yīng)對(duì)此類問題。

解決方案

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

方案 1:github.com/pkg/errors

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

  1. Wrap 方法用來包裝底層錯(cuò)誤,增加上下文文本信息并附加調(diào)用棧。一般用于包裝對(duì)第三方代碼(標(biāo)準(zhǔn)庫(kù)或第三方庫(kù))的調(diào)用。
  2. WithMessage 方法僅增加上下文文本信息,不附加調(diào)用棧。如果確定錯(cuò)誤已被 Wrap 過或不關(guān)心調(diào)用棧,可以使用此方法。注意:不要反復(fù) Wrap ,會(huì)導(dǎo)致調(diào)用棧重復(fù)
  3. Cause 方法用來判斷底層錯(cuò)誤 。

現(xiàn)在我們用這三個(gè)方法來重寫上面的代碼:

  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. */ 

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

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

注意:無論是 Wrap , WithMessage 還是 WithStack ,當(dāng)傳入的 err 參數(shù)為 nil 時(shí), 都會(huì)返回nil, 這意味著我們?cè)谡{(diào)用此方法之前無需作 nil 判斷,保持了代碼簡(jiǎn)潔

方案 2:golang.org/x/xerrors

結(jié)合社區(qū)反饋,Go 團(tuán)隊(duì)開始考慮在 Go 2 中簡(jiǎn)化錯(cuò)誤處理的提案。Go 核心團(tuán)隊(duì)成員 Russ Cox 在xerrors中部分實(shí)現(xiàn)了提案中的內(nèi)容。它用與 github.com/pkg/errors 相似的思路解決同一問題, 引入了一個(gè)新的 fmt 格式化動(dòng)詞: %w,使用 Is 進(jìn)行判斷。

  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 相比,它有幾點(diǎn)不足:

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

方案 3:Go 1.13 內(nèi)置支持

Go 1.13 將 xerrors 的部分功能(不是全部)整合進(jìn)了標(biāo)準(zhǔn)庫(kù)。它繼承了上面提到的 xerrors 的全部缺點(diǎn), 并額外貢獻(xiàn)了一項(xiàng)。因此目前沒有使用它的必要。

  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 版本非常接近。但是它不支持調(diào)用棧信息輸出, 根據(jù)官方的說法, 此功能沒有明確的支持時(shí)間。因此其實(shí)用性遠(yuǎn)低于 github.com/pkg/errors。

Golang 中將來可能的錯(cuò)誤處理方式

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

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

  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.     // ... 

感興趣的同學(xué)可以關(guān)注下這個(gè)提案:https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md

得出結(jié)論

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

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

總的來說,Go 在誕生之初就在各個(gè)方面表現(xiàn)得相當(dāng)成熟、穩(wěn)健。在演進(jìn)路線上很少出現(xiàn)猶疑和搖擺, 而在錯(cuò)誤處理方面卻是個(gè)例外。除了被廣泛吐槽的 if err != nil 之外, 就連其改進(jìn)路線也備受爭(zhēng)議、分歧明顯,以致于一個(gè)改進(jìn)提案都會(huì)因?yàn)閴旱剐缘姆磳?duì)意見而不得不作出調(diào)整。好在 Go 團(tuán)隊(duì)比以前更加樂于傾聽社區(qū)意見,團(tuán)隊(duì)甚至專門就此問題建了個(gè)反饋收集頁面。相信最終大家會(huì)找到更好的解決方案。

 

責(zé)任編輯:未麗燕 來源: Go Official Blog
相關(guān)推薦

2021-09-27 15:33:48

Go 開發(fā)技術(shù)

2021-09-27 10:04:03

Go程序處理

2017-12-21 07:54:07

2021-06-10 10:02:19

優(yōu)化緩存性能

2023-10-28 16:30:19

Golang開發(fā)

2012-12-19 09:36:49

測(cè)試自動(dòng)化測(cè)試

2024-12-27 10:51:53

2020-02-03 16:03:36

疫情思考

2009-06-25 09:50:32

JSF

2023-10-26 12:05:14

Golang開發(fā)

2015-10-12 08:59:57

異步代碼測(cè)試

2021-08-08 10:44:33

安卓系統(tǒng)開發(fā)者手機(jī)廠商

2021-06-15 07:10:14

JavaScript異步編程

2021-06-10 20:17:04

云網(wǎng)融合超融合

2025-03-18 09:20:00

Go語言Golang

2018-06-29 14:51:41

Java健壯性實(shí)踐

2021-09-28 06:28:51

EF錯(cuò)誤用法

2022-05-06 08:00:51

Golang編程語言Java

2011-11-30 15:57:18

2011-01-19 10:50:31

軟件設(shè)計(jì)師
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)