在 Golang 中返回未找到错误的惯用方法是什么?

3

我在Go语言中有一个带有以下签名的函数:

func GetAccount(ctx context.Context, id uuid.UUID) (*Account, error)

如果出现内部错误(例如数据库查询失败),它将返回错误,但是如果找不到该帐户,我不确定应该返回什么。我可以想到两种不同的方法:

  1. 如果未找到帐户,则只返回空帐户和空错误
  2. 返回自定义错误类型,如下所示:
type accountNotFoundErr struct {
    id uuid.UUID
}

func (err accountNotFoundErr) Error() string {
    return fmt.Sprintf("account not found for user: %v", err.id)
}

func IsAccountNotFoundErr(err error) bool {
    _, ok := err.(accountNotFoundErr)
    return ok
}

func GetAccount(ctx context.Context, id uuid.UUID) (*Account, error) {
    // if the account is not found
    return nil, accountNotFoundErr{id}
}

我喜欢第一种方法,因为它很简单,但我并不经常看到Go代码在错误非空时返回nil结果。我认为期望是,如果错误为零,则结果有效。第二种方法解决了这个问题,但对调用者来说也更加复杂。
在Go中处理这种情况的惯用方法是什么?

6
您应该决定是否找不到账户是一个错误。这可能因功能而异 / 每个用例而异。例如,如果在登录期间用户指定了用户名,查找该帐户可能很容易不存在(用户可能输错了用户名)。但是,如果您想查找已登录的帐户,如果他/她已经登录,则帐户必须存在,在这种情况下找不到帐户确实意味着错误。无论如何,始终记得记录您的函数行为方式是很好的。 - icza
1个回答

3

我阅读了很多关于Go语言中自定义错误的文章。其中大部分都创建了自己的结构体来实现错误接口。

我发现这种方法存在的问题是,我无法轻松地检查错误是否属于某种特定的类型。同样地,您可能能够检查一些标准库错误,比如if error == EOF

因此,我最喜欢的方法是使用erros.New创建一个简单的变量。

var ErrNotFound = errors.New("Resource was not found")

func main() {
    err := raise()
    if err == ErrNotFound {
        fmt.Println("impossibru")
        return
    }
    if err != nil {
        fmt.Println("unexpected error")
        return
    }
}

func raise() error {
    return ErrNotFound
}

https://play.golang.com/p/s0ZQfsdLqxB

正如评论中@Gavin指出的那样,如果您想通过使用fmt.Errorf将特定错误包装起来来提供更多上下文信息,则需要使用errors.Is检查该特定错误是否已被包装。

var ErrNotFound = errors.New("Resource was not found")

func main() {
    err := raise(42)
    if errors.Is(err, ErrNotFound) {
        fmt.Println(err)
        return
    }
    if err != nil {
        fmt.Println("unexpected error")
        return
    }
}

func raise(id int) error {
    return fmt.Errorf("id %d does not exist, error: %w", id, ErrNotFound)
}

https://play.golang.com/p/hSrkb1Xp4Hn


1
如果错误被包装起来,这种方法会失败。相反,你应该使用 if errors.Is(err, ErrNotFound)。https://play.golang.com/p/536y_bh7_jy - Gavin
我们应该对每一个错误都这样处理吗?还是只处理那些可能被包装的错误?因为我看到大部分代码并没有使用 errors.Is。例如 if err := server.ListenAndServe(); err != http.ErrServerClosed {} - The Fool
但是你的例子很好。我从未使用过那种技术。有时增加一些更多的上下文是有意义的。 - The Fool
你提供的 ListenAndServe() 示例并不关心返回的错误是什么,只要有错误就行。如果你对特定的错误感兴趣,那么几乎总是应该使用 errors.Is - Gavin
@Gavin,好的,谢谢你的提示。我已经把它加到我的答案里了。 - The Fool
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接