日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[译]Go如何优雅的处理异常

發布時間:2024/9/21 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [译]Go如何优雅的处理异常 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文:hackernoon.com/golang-hand…

注:譯文中error可以理解為異常,但Go中的error和Java中的異常還是有很大區別的,需要讀者慢慢體會,所以為了方便閱讀和思考,譯文中的名詞error就不翻譯了。

正文

??Go有一套簡單的error處理模型,但其實并不像看起來那么簡單。本文中,我會提供一種好的方法去處理error,并用這個方法來解決在往后編程遇到的的類似問題。

??首先,我們會分析下Go中的error。

??接著我們來看看error的產生和error的處理,再分析其中的缺陷。

??最后,我們將要探索一種方法來解決我們在程序中遇到的類似問題。

什么是error?

??看下error在內置包中的定義,我們可以得出一些結論:

// error類型在內置包中的定義是一個簡單的接口 // 其中nil代表沒有異常 type error interface {Error() string } 復制代碼

??從上面的代碼,我們可以看到error是一個接口,只有一個Error方法。

??那我們要實現error就很簡單了,看以下代碼:

type MyCustomError stringfunc (err MyCustomError) Error() string {return string(err) } 復制代碼

??下面我們用標準包fmt和errors去聲明一些error:

import ("errors""fmt" )simpleError := errors.New("a simple error") simpleError2 := fmt.Errorf("an error from a %s string", "formatted") 復制代碼

??思考:上面的error定義中,只有這些簡單的信息,就足夠處理好異常嗎?我們先不著急回答,下面我們去尋找一種好的解決方法。

error處理流

??現在我們已經知道了在Go中的error是怎樣的了,下一步我們來看下error的處理流程。

??為了遵循簡約和DRY(避免重復代碼)原則,我們應該只在一個地方進行error的處理。

??我們來看下以下的例子:

// 同時進行error處理和返回error // 這是一種糟糕的寫法 func someFunc() (Result, error) {result, err := repository.Find(id)if err != nil {log.Errof(err)return Result{}, err}return result, nil } 復制代碼

??上面這段代碼有什么問題呢?

??我們首先打印了這個error信息,然后又將error返回給函數的調用者,這相當于重復進行了兩次error處理。

??很有可能你組里的同事會用到這個方法,當出現error時,他很有可能又會將這個error打印一遍,然后重復的日志就會出現在系統日志里了。

??我們先假設程序有3層結構,分別是數據層,交互層和接口層:

// 數據層使用了一個第三方orm庫 func getFromRepository(id int) (Result, error) {result := Result{ID: id}err := orm.entity(&result)if err != nil {return Result{}, err}return result, nil } 復制代碼

??根據DRY原則,我們可以將error返回給調用的最上層接口層,這樣我們就能統一的對error進行處理了。

??但上面的代碼有一個問題,Go的內置error類型是沒有調用棧的。另外,如果error產生在第三方庫中,我們還需要知道我們項目中的哪段代碼負責了這個error。

??github.com/pkg/errors 可以使用這個庫來解決上面的問題。

??利用這個庫,我對上面的代碼進行了一些改進,加入了調用棧和加了一些相關的錯誤信息。

import "github.com/pkg/errors"// 使用了第三方的orm庫 func getFromRepository(id int) (Result, error) {result := Result{ID: id}err := orm.entity(&result)if err != nil {return Result{}, errors.Wrapf(err, "error getting the result with id %d", id);}return result, nil } // 當error封裝完后,返回的error信息將會是這樣的 // err.Error() -> error getting the result with id 10 // 這就很容易知道這是來自orm庫的error了 復制代碼

??上面的代碼對orm的error進行了封裝,增加了調用棧,而且沒有修改原始的error信息。

?? 然后我們再來看看在其他層是如何處理這個error的,首先是交互層:

func getInteractor(idString string) (Result, error) {id, err := strconv.Atoi(idString)if err != nil {return Result{}, errors.Wrapf(err, "interactor converting id to int")}return repository.getFromRepository(id) } 復制代碼

??接著是接口層:

func ResultHandler(w http.ResponseWriter, r *http.Request) {vars := mux.Vars(r)result, err := interactor.getInteractor(vars["id"])if err != nil {handleError(w, err)}fmt.Fprintf(w, result) } 復制代碼func handleError(w http.ResponseWriter, err error) {// 返回HTTO 500錯誤w.WriteHeader(http.StatusIntervalServerError)log.Errorf(err)fmt.Fprintf(w, err.Error()) } 復制代碼

?? 現在我們只在最上層接口層處理了error,看起來很完美?并不是,如果程序中經常返回HTTP錯誤碼500,同時將錯誤打印到日志中,像result not found這種沒用的日志就會很煩人。

解決方法

?? 我們上面討論到僅僅靠一個字符串是不足以處理好error的。我們也知道通過給error加一些額外的信息就能追溯到error的產生和最后的處理邏輯。

?? 因此我定義了三個error處理的宗旨。

error處理的三個宗旨

  • 提供清晰完整的調用棧
  • 必要時提供error的上下文信息
  • 打印error到日志中(例如可以在框架層打印)

我們來創建一個error類型:

const(NoType = ErrorType(iota)BadRequestNotFound// 可以加入你需要的error類型 )type ErrorType uinttype customError struct {errorType ErrorTypeoriginalError errorcontextInfo map[string]string }// 返回customError具體的錯誤信息 func (error customError) Error() string {return error.originalError.Error() }// 創建一個新的customError func (type ErrorType) New(msg string) error {return customError{errorType: type, originalError: errors.New(msg)} }// 給customError自定義錯誤信息 func (type ErrorType) Newf(msg string, args ...interface{}) error {err := fmt.Errof(msg, args...)return customError{errorType: type, originalError: err} }// 對error進行封裝 func (type ErrorType) Wrap(err error, msg string) error {return type.Wrapf(err, msg) }// 對error進行封裝,并加入格式化信息 func (type ErrorType) Wrapf(err error, msg string, args ...interface{}) error {newErr := errors.Wrapf(err, msg, args..)return customError{errorType: errorType, originalError: newErr} } 復制代碼

?? 從上面的代碼可以看到,我們可以創建一個新的error類型或者對已有的error進行封裝。但我們遺漏了兩件事情,一是我們不知道error的具體類型。二是我們不知道怎么給這這個error加上下文信息。

?? 為了解決以上問題,我們來對github.com/pkg/errors的方法也進行一些封裝。

// 創建一個NoType error func New(msg string) error {return customError{errorType: NoType, originalError: errors.New(msg)} }// 創建一個加入了格式化信息的NoType error func Newf(msg string, args ...interface{}) error {return customError{errorType: NoType, originalError: errors.New(fmt.Sprintf(msg, args...))} }// 給error封裝多一層string func Wrap(err error, msg string) error {return Wrapf(err, msg) }// 返回最原始的error func Cause(err error) error {return errors.Cause(err) }// error加入格式化信息 func Wrapf(err error, msg string, args ...interface{}) error {wrappedError := errors.Wrapf(err, msg, args...)if customErr, ok := err.(customError); ok {return customError{errorType: customErr.errorType,originalError: wrappedError,contextInfo: customErr.contextInfo,}}return customError{errorType: NoType, originalError: wrappedError} } 復制代碼

接著我們給error加入上下文信息:

// AddErrorContext adds a context to an error func AddErrorContext(err error, field, message string) error {context := errorContext{Field: field, Message: message}if customErr, ok := err.(customError); ok {return customError{errorType: customErr.errorType, originalError: customErr.originalError, contextInfo: context}}return customError{errorType: NoType, originalError: err, contextInfo: context} }// GetErrorContext returns the error context func GetErrorContext(err error) map[string]string {emptyContext := errorContext{}if customErr, ok := err.(customError); ok || customErr.contextInfo != emptyContext {return map[string]string{"field": customErr.context.Field, "message": customErr.context.Message}}return nil }// GetType returns the error type func GetType(err error) ErrorType {if customErr, ok := err.(customError); ok {return customErr.errorType}return NoType } 復制代碼

現在將上述的方法應用在我們文章開頭寫的example中:

import "github.com/our_user/our_project/errors" // The repository uses an external depedency orm func getFromRepository(id int) (Result, error) {result := Result{ID: id}err := orm.entity(&result)if err != nil {msg := fmt.Sprintf("error getting the result with id %d", id)switch err {case orm.NoResult:err = errors.Wrapf(err, msg);default:err = errors.NotFound(err, msg);}return Result{}, err}return result, nil } // after the error wraping the result will be // err.Error() -> error getting the result with id 10: whatever it comes from the orm 復制代碼func getInteractor(idString string) (Result, error) {id, err := strconv.Atoi(idString)if err != nil {err = errors.BadRequest.Wrapf(err, "interactor converting id to int")err = errors.AddContext(err, "id", "wrong id format, should be an integer")return Result{}, err}return repository.getFromRepository(id) }func ResultHandler(w http.ResponseWriter, r *http.Request) {vars := mux.Vars(r)result, err := interactor.getInteractor(vars["id"])if err != nil {handleError(w, err)}fmt.Fprintf(w, result) } 復制代碼func handleError(w http.ResponseWriter, err error) {var status interrorType := errors.GetType(err)switch errorType {case BadRequest:status = http.StatusBadRequestcase NotFound:status = http.StatusNotFounddefault:status = http.StatusInternalServerError}w.WriteHeader(status)if errorType == errors.NoType {log.Errorf(err)}fmt.Fprintf(w,"error %s", err.Error())errorContext := errors.GetContext(err)if errorContext != nil {fmt.Printf(w, "context %v", errorContext)} } 復制代碼

?? 通過簡單的封裝,我們可以明確的知道error的錯誤類型了,然后我們就能方便進行處理了。

?? 讀者也可以將代碼運行一遍,或者利用上面的errors庫寫一些demo來加深理解。

感謝閱讀,歡迎大家指正,留言交流~

轉載于:https://juejin.im/post/5cee8ec1518825477a52dee8

總結

以上是生活随笔為你收集整理的[译]Go如何优雅的处理异常的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。