日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
關(guān)于Golang錯誤處理的一些思考

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

我們注重客戶提出的每個要求,我們充分考慮每一個細節(jié),我們積極的做好成都做網(wǎng)站、網(wǎng)站設(shè)計服務(wù),我們努力開拓更好的視野,通過不懈的努力,成都創(chuàng)新互聯(lián)公司贏得了業(yè)內(nèi)的良好聲譽,這一切,也不斷的激勵著我們更好的服務(wù)客戶。 主要業(yè)務(wù):網(wǎng)站建設(shè),網(wǎng)站制作,網(wǎng)站設(shè)計,微信小程序定制開發(fā),網(wǎng)站開發(fā),技術(shù)開發(fā)實力,DIV+CSS,PHP及ASP,ASP.Net,SQL數(shù)據(jù)庫的技術(shù)開發(fā)工程師。

關(guān)于 Golang 錯誤處理的實踐

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

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

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

基于字符串的錯誤

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

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

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

拋出問題

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

 
 
 
  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. }
  17. //Outputs:
  18. // got err, sql: no rows in result set

有時需要根據(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. }
  21. //Outputs:
  22. // data not found, sql: no rows in result set

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

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

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

解決方案

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

方案 1:github.com/pkg/errors

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

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

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

 
 
 
  1. import (
  2.    "database/sql"
  3.    "fmt"
  4.    "github.com/pkg/errors"
  5. )
  6. func GetSql() error {
  7.    return errors.Wrap(sql.ErrNoRows, "GetSql failed")
  8. }
  9. func Call() error {
  10.    return errors.WithMessage(GetSql(), "bar failed")
  11. }
  12. func main() {
  13.    err := Call()
  14.    if errors.Cause(err) == sql.ErrNoRows {
  15.       fmt.Printf("data not found, %v\n", err)
  16.       fmt.Printf("%+v\n", err)
  17.       return
  18.    }
  19.    if err != nil {
  20.       // unknown error
  21.    }
  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ù),那么錯誤信息會保持一行, 其中依次包含調(diào)用棧的上下文文本。使用 %+v ,則會輸出完整的調(diào)用棧詳情。如果不需要增加額外上下文信息,僅附加調(diào)用棧后返回,可以使用 WithStack 方法:

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

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

方案 2:golang.org/x/xerrors

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

 
 
 
  1. import (
  2.    "database/sql"
  3.    "fmt"
  4.    "golang.org/x/xerrors"
  5. )
  6. func Call() error {
  7.    if err := GetSql(); err != nil {
  8.       return xerrors.Errorf("bar failed: %w", GetSql())
  9.    }
  10.    return nil
  11. }
  12. func GetSql() error {
  13.    return xerrors.Errorf("GetSql failed: %w", sql.ErrNoRows)
  14. }
  15. func main() {
  16.    err := Call()
  17.    if xerrors.Is(err, sql.ErrNoRows) {
  18.       fmt.Printf("data not found, %v\n", err)
  19.       fmt.Printf("%+v\n", err)
  20.       return
  21.    }
  22.    if err != nil {
  23.       // unknown error
  24.    }
  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 不位于于格式化字符串的結(jié)尾,或冒號與百分號之間沒有空格,包裝將失效且不報錯;
  • 而且,調(diào)用 xerrors.Errorf 之前需要對參數(shù)進行nil判斷。這完全沒有簡化開發(fā)者的工作

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

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. }
  25. /* Outputs:
  26. data not found,  Call failed: GetSql failed: sql: no rows in result set
  27. */

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

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

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

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

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

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

得出結(jié)論

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

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

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


分享標題:關(guān)于Golang錯誤處理的一些思考
文章出自:http://m.5511xx.com/article/cogooii.html