Return nil or custom error in Go

huangapple go评论86阅读模式
英文:

Return nil or custom error in Go

问题

在Go语言中使用自定义的Error类型(带有额外字段来捕获一些细节)时,当尝试将nil作为该类型的值返回时,会出现编译错误,例如cannot convert nil to type DetailedErrorcannot use nil as type DetailedError in return argument,代码大致如下:

type DetailedError struct {
    x, y int
}

func (e DetailedError) Error() string {
    return fmt.Sprintf("Error occurred at (%s,%s)", e.x, e.y)
}

func foo(answer, x, y int) (int, DetailedError) {
    if answer == 42 {
        return 100, nil  //!! cannot use nil as type DetailedError in return argument
    }
    return 0, DetailedError{x: x, y: y}
}

(完整代码段:https://play.golang.org/p/4i6bmAIbRg)

解决这个问题的惯用方式是什么?(或者任何可行的方式...)

实际上,我需要在错误上有额外的字段,因为我通过复杂的逻辑从简单的错误构建详细的错误消息等等。如果我只是回退到"字符串错误",我基本上需要将这些字符串解析成片段,并根据它们进行逻辑处理等等,这似乎非常丑陋(我的意思是,为什么要将信息序列化为字符串,而后面你知道你需要反序列化...)

英文:

When using a custom Error type in Go (with extra fields to capture some details), when trying to return nil as value of this type, I get compile errors like cannot convert nil to type DetailedError or like cannot use nil as type DetailedError in return argument, from code looking mostly like this:

type DetailedError struct {
	x, y int
}

func (e DetailedError) Error() string {
	return fmt.Sprintf("Error occured at (%s,%s)", e.x, e.y)
}

func foo(answer, x, y int) (int, DetailedError) {
	if answer == 42 {
		return 100, nil  //!! cannot use nil as type DetailedError in return argument
	}
	return 0, DetailedError{x: x, y: y}
}

(full snippet: https://play.golang.org/p/4i6bmAIbRg)

What would be the idiomatic way to solve this problem? (Or any way that works...)

I actually need the extra fields on errors because I have detailed error messages constructed by complex logic from simpler ones etc., and if I would just fall back to "string errors" I'd basically have to parse those strings to pieces and have logic happen based on them and so on, which seems really ugly (I mean, why serialize to strings info you know you're gonna need to deserialize later...)

答案1

得分: 22

不要将DetailedError用作返回类型,始终使用error

func foo(answer, x, y int) (int, error) {
    if answer == 42 {
        return 100, nil  //!! 无法将nil用作返回参数中的DetailedError类型
    }
    return 0, DetailedError{x: x, y: y}
}

你的DetailedError类型满足error接口的要求就足够了。然后,在调用者中,如果你关心额外的字段,可以使用类型断言:

value, err := foo(...)
if err != nil {
    if detailedErr, ok := err.(DetailedError); ok {
        // 使用详细的错误值做一些处理
    } else {
        // 这是其他类型的错误,根据情况进行处理
    }
}

不返回DetailedError的原因:

现在可能看起来并不重要,但将来你的代码可能会扩展以包含其他错误检查:

func foo(answer, x, y int) (int, error) {
    cache, err := fetchFromCache(answer, x, y)
    if err != nil {
        return 0, fmt.Errorf("无法读取缓存:%s", err)
    }
    // ... 
}

额外的错误类型将不是DetailedError类型,因此你必须返回error

此外,你的方法可能会被其他不知道或不关心DetailedError类型的调用者使用:

func fooWrapper(answer, x, y int) (int, error) {
    // 在调用foo之前做一些处理
    result, err := foo(answer, x, y)
    if err != nil {
        return 0, err
    }
    // 在调用foo之后做一些处理
    return result, nil
}

期望每个函数的调用者都理解自定义错误类型是不合理的,这正是为什么在Go中存在接口,特别是error接口的原因。

利用它,不要规避它。

即使你的代码永远不会改变,为每个函数或用例创建一个新的自定义错误类型是不可持续的,会使你的代码难以阅读和无法推理。

英文:

Don't use DetailedError as a return type, always use error:

func foo(answer, x, y int) (int, error) {
    if answer == 42 {
        return 100, nil  //!! cannot use nil as type DetailedError in return argument
    }
    return 0, DetailedError{x: x, y: y}
}

The fact that your DetailedError type satisfies the error interface is sufficient to make this work. Then, in your caller, if you care about the extra fields, use a type assertion:

value, err := foo(...)
if err != nil {
    if detailedErr, ok := err.(DetailedError); ok {
        // Do something with the detailed error values
    } else {
        // It's some other error type, behave accordingly
    }
}

Reasons for not returning DetailedError:

It may not appear to matter right now, but in the future your code may expand to include additional error checks:

func foo(answer, x, y int) (int, error) {
    cache, err := fetchFromCache(answer, x, y)
    if err != nil {
        return 0, fmt.Errorf("Failed to read cache: %s", err)
    }
    // ... 
}

The additional error types won't be of DetailedError type, so you then must return error.

Further, your method may be used by other callers that don't know or care about the DetailedError type:

func fooWrapper(answer, x, y int) (int, error) {
    // Do something before calling foo
    result, err := foo(answer, x, y)
    if err != nil {
        return 0, err
    }
    // Do something after calling foo
    return result, nil
}

It's unreasonable to expect every caller of a function to understand a custom error type--this is precisely why interfaces, and in particular the error interface, exists in Go.

Take advantage of this, don't circumvent it.

Even if your code never changes, having a new custom error type for every function or use-case is unsustainable, and makes your code unreadable and impossible to reason about.

答案2

得分: 2

DetailedError结构体的零值不是nil,而是DetailedError{}。你可以选择返回error接口而不是DetailedError

func foo(answer, x, y int) (int, error) {

或者使用指针

func foo(answer, x, y int) (int, *DetailedError) {
...
//and
func (e *DetailedError) Error() string {
英文:

DetailedError struct zero value isn't nil but DetailedError{}. You can either return error interface instead of DetailedError

func foo(answer, x, y int) (int, error) {

or use pointer

func foo(answer, x, y int) (int, *DetailedError) {
...
//and
func (e *DetailedError) Error() string {

答案3

得分: 0

解决您的问题的惯用方法是返回错误接口。

如果您实际上需要一个不是错误接口的函数,您应该创建一个扩展错误接口的新接口。

type DetailedErrorInterface interface {
  error
  GetX() int //需要实现
  GetY() int //需要实现
}

然后,您必须更改函数以返回此接口:

func foo(answer, x, y int) (int, DetailedErrorInterface) {
  if answer == 42 {
    return 100, nil
  }
  return 0, DetailedError{x: x, y: y}
}
英文:

The idiomatic way to solve your problem is to return the error interface.

If you actually need a function that is not part of the error interface, you should create a new interface that extends the error interface.

type DetailedErrorInterface interface {
  error
  GetX() int //need to implement
  GetY() int //need to implement
}

Then, you must change your function to return this interface:

func foo(answer, x, y int) (int, DetailedErrorInterface) {
  if answer == 42 {
    return 100, nil
  }
  return 0, DetailedError{x: x, y: y}
}

huangapple
  • 本文由 发表于 2017年9月3日 18:32:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/46022517.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定