首页 > 解决方案 > 如何检测自定义错误

问题描述

动机

我有一个名为CustomError的自定义错误类型,我想编写一个将任何类型的错误解析到我的错误结构中的方法。所以我写了parse方法给你看。我将在任何返回错误接口的函数上使用parse方法。因此,所有错误都将根据我的类型进行结构化。

问题

当我使用带有 nil 值的parse方法返回错误接口时,返回的错误不是 nil。代码如下。第一次测试成功,但第二次没有。

const (
    UnHandledError      = "UnHandledError"
    CircuitBreakerError = "CircuitBreakerError"
)

type CustomErrorType struct {
    Key     string
    Message string
    Status  int
}

func (c *CustomErrorType) Error() string {
    return "Custom error message"
}

func parse(err error) *CustomErrorType {
    if err == nil {
        return nil
    }
    if e, ok := err.(*CustomErrorType); ok {
        return e
    }
    if e, ok := err.(hystrix.CircuitError); ok {
        return &CustomErrorType{CircuitBreakerError, e.Message, http.StatusTooManyRequests}
    }
    return &CustomErrorType{UnHandledError, err.Error(), http.StatusInternalServerError}
}

func TestParse_it_should_return_nil_when_error_is_nil(t *testing.T) {
    result := parse(nil)
    if result != nil {
        t.Error("result is not nil")
    }
}

func TestParse_it_should_return_nil_when_error_is_nil_2(t *testing.T) {
    aFunc := func() error {
        return parse(nil)
    }
    result := aFunc()
    if result != nil {
        t.Error("result is not nil")
    }
}

你能解释一下我错过了什么或有什么问题吗?

标签: goerror-handling

解决方案


这是 go 接口的一个常见“问题”的一个实例,它是由底层接口的实际实现引起的:包含nil指针的接口不是 - nil

它在 go's faq 中描述了一个类似于你的error接口情况的例子:为什么我的 nil 错误值不等于 nil?

在幕后,接口被实现为两个元素,一个类型T和一个值VV是一个具体的值,例如 an或int,从不是接口本身,并且具有 type 。structpointerT

...

接口值nil仅在VT都未设置时,(T=nil, V is not set),特别是,nil接口将始终保存一个nil类型。如果我们将nil类型指针存储*int在接口值中,则内部类型将*int与指针的值无关:(T=*int, V=nil)V因此,即使内部的指针值为 ,这样的接口值也将非零nil

这种情况可能会令人困惑,并且当nil值存储在接口值中时会出现这种情况,例如错误返回:

func returnsError() error {
  var p *MyError = nil
  if bad() {
      p = ErrBad
  }
  return p // Will always return a non-nil error.
}

此示例类似于您执行此操作时代码中发生的情况:

aFunc := func() error {
    return parse(nil)
}

parse()返回*CustomErrorType,但只是error使值返回的函数返回一个包含类型和nil值的接口:(T=*CustomErrorType, V=nil)反过来,它的计算结果为 not- nil

然后常见问题解答继续提供解释并显示“正确”示例:

如果一切顺利,函数返回一个nilp,所以返回值是一个error接口值holding (T=*MyError, V=nil)。这意味着,如果调用者将返回的error与进行比较nil,即使没有发生任何不好的事情,它也总是看起来好像有错误。要向调用者返回正确的nil错误,函数必须返回显式nil

func returnsError() error {
  if bad() {
      return ErrBad
  }
  return nil
}

在您的示例中也可以观察到该行为,添加一个

fmt.Printf("%#v\n", result)

打印result的值:

(*e.CustomErrorType)(nil)

如果我们将parse()返回类型更改为 just error,它将打印:

<nil>

推荐阅读