关闭。这个问题是opinion-based .它目前不接受答案。
想改进这个问题?更新问题,以便 editing this post 可以用事实和引用来回答它.
2年前关闭。
Improve this question
这是一个与 this 非常相似的问题。 ,但专注于 Go 实现。
假设您有一个使用某个 ID 获取对象的函数。该函数可能找不到该对象。
就像是:
func FindUserByID(id int) *User {
....
}
找不到用户的情况应该如何处理?
有许多模式和解决方案可供遵循。有些很常见,有些在特定语言中很受欢迎。
我需要选择一个适合 Go (golang) 的解决方案,所以我想了解所有选项并获得一些关于这里最好的方法的反馈。
Go 有一些限制和一些功能可以帮助解决这个问题。
选项1:
显而易见的解决方案是让“Find”函数返回 nil,以防找不到对象。许多人都知道,返回 nil 是不利的,它会迫使调用者检查 nil,使“nil”成为“未找到”的神奇值。
func FindUserByID(id int) *User {
...
if /*user not found*/ {
return nil
}
...
}
有用。这很简单。
选项 2:
返回异常 - “未找到”。
Goland 不支持异常。唯一的选择是让函数返回错误,并检查调用者代码中的错误:
func FindUserByID(id int) (*User, error) {
...
return errors.New("NotFound")
}
func Foo() {
User, err := FindUserByID(123)
if err.Error() == "NotFound" {
...
}
...
}
由于 Go 不支持异常,以上是代码异味,依赖于错误字符串。
选项 3:
分为两个不同的功能:一个将检查对象是否存在,另一个将返回它。
func FindUserByID(id int) *User {
....
}
func IsExist(id int) bool {
...
}
它的问题是:
选项 4:
更改“查找”的名称以表明它可能返回 nil。这在 .Net 中很常见,名称可以是“TryFindByID”。
但是很多人讨厌这种模式,我在其他任何地方都没有见过。无论如何,它仍然使 nil 值成为神奇的“不存在”标记。
选项 5:
在某些语言(java、c++)中有“可选”模式。这会形成一个清晰的签名并帮助调用者了解她需要先调用“isEmpty()”。
不幸的是,这在 Go 中不可用。 github中有一些项目(如https://github.com/markphelps/optional),但由于Go是有限的并且不支持在没有强制转换的情况下返回泛型类型,这意味着需要另一个编译步骤来为out对象创建一个可选结构,并在函数签名中使用它.
func FindUserByID(id int) OptionalUser {
....
}
func Foo() {
optionalUser := FindUserByID(123)
if optionalUser.IsEmpty() {
...
}
...
}
但这取决于第 3 方并增加了编译复杂性。它使遵循这种模式的结构的数量增加了一倍。
选项 6:
Go 支持在一个函数中返回多个值。因此,如果对象存在,“查找”函数也可以返回一个 bool 值。
func FindUserByID(id int) (*User, bool) {
...
if /*user not found*/ {
return nil, false
}
...
}
这似乎是 Go 中的一种有利方法。例如,Go 中的强制转换也会返回一个 bool 值,说明操作是否成功。
我想知道最好的方法是什么,并获得有关上述选项的一些反馈。
已编辑:将用户更改为指针(更好地为示例服务)
最佳答案
这个问题基本上是基于意见的,因此我不愿回答。但是这里发生的事情已经够多了,我认为它需要一个答案,而不仅仅是评论。
你给出了 6 个选项,但实际上只有 3 个。你的 6 个选项中的大多数都属于我的第一类。在底部,我将对您的每个建议进行具体的批评。
在这种情况下,我的偏好是始终返回一个文字 bool 值(但强调:这是我的观点,不是客观事实):
func FindUserByID(id string) (*User, bool)
在您的 6 个选项中,这对于任何偶然阅读代码的人来说都是最明显的。对于是否可能返回 nil 值,或者您是否必须查找一些复杂的 API 来进行两阶段查找等,它不会留下任何猜测。
如果您可以选择其他错误状态(例如超时、数据库错误、格式错误的输入等),那么简单的 bool 值是不够的,您必须返回错误(或 panic ,稍后会详细介绍)。在这种情况下,我的偏好(再次:我的观点)是使用错误来传达 not-found :
func FindUserByID(id string) (*User, error)
在这种情况下,您可以使用 sentinel error value这很容易从您的来电者那里检查:
var ErrNotFound = errors.New("not found")
func FindUserByID(id string) (*User, error) {
/* couldn't find the user, so... */
return nil, ErrNotFound
}
以及代码中的其他地方...
user, err := database.FindUserByID("1234")
if err == database.ErrNotFound {
/* behave accordingly */
}
不过,一般来说,哨兵错误并不是最佳实践。通常最好通过接口(interface)传达错误类型,但这超出了这个问题的范围。 Further reading here if you're interested .
最后的选择是 panic (即其他语言中的“抛出异常”)。但实际上在所有情况下都应该避免这种情况,这绝对是这种功能的错误方法。我只是在这里提到它的完整性。 不要这样做
func FindUserByID(id string) *User {
/* Couldn't find the user so... */
panic("Can't find the user!")
}
这是我对您的 6 个选项的具体批评(再次:我的观点)。
这是不直观的。我的建议是仅在零值(在您的情况下为
nil
)本身有效时才执行此操作。它可能不适合用户。可能是为了计数。 您的示例实际上只返回一个错误,而不是异常( panic )。返回错误是一个完全有效的选项,但不要求助于检查错误字符串。请参阅我上面的讨论。
这是不直观的,而且很活泼。如何使用这个繁琐的 API 并不明显,所以我会避免它。这也很有趣,因为无法保证在检查和检索之间没有创建新用户或删除现有用户。
这将比选项 #1 略有改进,但不是惯用的 Go。有更好的选择。
这只是返回 bool 值的繁琐方式。更喜欢实际的 bool 值。
这是一个有效的选项,在整个标准库中都可以看到。如果没有其他错误条件是可能的,这是合适的。见上面的评论。
关于function - 对于获取对象但可能找不到它的函数,最好的 func 签名是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60828105/