if err !=nil之殇…Go error是这么处理的

2022年5月18日 398点热度 0人点赞 0条评论

最近在学习Golang,所以将学习过程记为笔记,以后翻阅的时候也方便,顺便也给大家做一点分享,希望能坚持下去。


关注本公众号,即可领取视频教程

2022年GO语言全套精讲系列-入门到精通96集


学习与交流:Go语言技术微信群

商务合作加微信:LetsFeng


现在就开始你的Go语言学习之旅吧!人生苦短,let’s Go.



图片

图片

很多人使用Go语言的时候恐怕都被error折腾过,特别是当你受到有try/catch特性的语言影响的时候,例如:Java、PHP 等。 


老实说,确实没有完美的技术,本质上都是权衡的结果,比如error同样有让人惊艳的轻量级和简单设计的特性。

error就是一个普通的值,处理起来没有额外的开销;

error 的扩展性很不错,可以按照不同的场景来自定义错误;

1.13之后的版本提供了一些函数,让错误的处理和追踪更方便了。

Go 语言的流行及广阔前景在业内基本上已经达成共识,除了像字节、腾讯等大厂不断加码之外,中小企业也越来越多用Go来实现项目,而error是Go语言开发者必须得掌握的技术要点。

 

由于Go语言对错误的设计方式,导致程序中可能出现大量的 if err !=nil{return err } return nil。有时候明明两行代码就能解决的事,由于error,我们可能需要10行。的确容易让人暴躁…

 

error到底该怎么用?最佳实践到底是怎么样的?如何通过一些编码技巧减少类似if err != nil这种代码?error如何把报错的路径完整记录下来?

Go语言的错误处理没有堆栈跟踪,所以如果抛出异常,我们无法追踪到底是哪一行发生的错误。


pkg/errors库弥补了这个不足。

我们明确的指定错误抛出位置的信息。

import "github.com/pkg/errors"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 
}


上面这样处理后,发生错误时,返回的错误信息,如下:

// err.Error() -> error getting the result with id 10: whatever it comes from the orm

这个函数的作用就是封装来自ORM的错误,在不影响原始信息的情况下添加了堆栈跟踪的功能。


interactor层的用法,代码如下:

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) 
}

顶层web server的用法,代码如下:

r := mux.NewRouter()
r.HandleFunc("/result/{id}", ResultHandler)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) { 
   w.WriteHeader(http.StatusIntervalServerError)
   log.Errorf(err)
   fmt.Fprintf(w, err.Error())}

顶层的错误处理方法其实也是不完美的。为什么呢?因为都是一些500的HTTP CODE,没什么用,给日志文件添加的都是无用的数据。

优雅的用法

上面的例子在web server层处理错误,不完美啊,都混沌了。

如果我们在错误中引入新的内容,我们将以某种方式在创建错误的地方和最终处理错误的时候引入依赖项。

下面定义3个目标的解决方案:

1 提供良好的错误堆栈跟踪

2 web层面的错误日志

3 必要时为用户提供上下文错误信息。(例如:所提供的电子邮件格式不正确)

首先创建一个错误类型。

package errors
const(
  NoType = ErrorType(iota)
  BadRequest
  NotFound
  //添加任意其他项。)type ErrorType uinttype customError struct {
  errorType ErrorType
  originalError error 
  contextInfo map[string]string 
}// Error 函数返回  customError func (error customError) Error() string {
   return error.originalError.Error()}// 新建一个 customError 结构func (type ErrorType) New(msg stringerror {
   return customError{errorType: type, originalError: errors.New(msg)}}// 新建一个格式化的错误信息func (type ErrorType) Newf(msg string, args ...interface{}) error {    
   err := fmt.Errof(msg, args...)

   return customError{errorType: type, originalError: err}}// 修饰函数。func (type ErrorType) Wrap(err error, msg stringerror {
   return type.Wrapf(err, msg)}// 修饰函数中返回可视化的错误信息。func (type ErrorType) Wrapf(err error, msg string, args ...interface{}) error { 
   newErr := errors.Wrapf(err, msg, args..)   

   return customError{errorType: errorType, originalError: newErr}}

正如上面代码所示,只有ErrorType 和错误类型是公开可访问的。我们可以创建任意新的错误,或修饰已存在的错误。

但是有两件事情没有做到:

如何在不导出customError的情况下检查错误类型?

我们如何向错误中添加/获取上下文,甚至是向外部依赖项中已存在的错误中添加上下文?


改进上面的代码:

func New(msg string) error {
   return customError{errorType: NoType, originalError: errors.New(msg)}}func Newf(msg string, args ...interface{}) error {
   return customError{errorType: NoType, originalError: errors.New(fmt.Sprintf(msg, args...))}}func Wrap(err error, msg string) error {
   return Wrapf(err, msg)}func Cause(err error) error {
   return errors.Cause(err)}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}}

现在让我们建立我们的方法处理上下文和任何一般错误的类型:

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}}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}func GetType(err error) ErrorType {
   if customErr, ok := err.(customError); ok {
      return customErr.errorType
   }

   return NoType
}

现在回到我们的例子,我们要应用这个新的错误包:

import "github.com/our_user/our_project/errors"// The repository 使用了外部依赖项 ormfunc 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 
}

interactor层的写法:

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) 
}

最后web server层的写法:

r := mux.NewRouter()
r.HandleFunc("/result/{id}", ResultHandler)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 int
   errorType := errors.GetType(err)
   switch errorType {
     case BadRequest: 
      status = http.StatusBadRequest
     case NotFound: 
      status = http.StatusNotFound
     default
      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)
   }}

参考链接:https://cloud.tencent.com/developer/article/1621058

更多相关Go语言的技术文章或视频教程,请关注本公众号获取并查看,感谢你的支持与信任!

28200if err !=nil之殇…Go error是这么处理的

这个人很懒,什么都没留下

文章评论