function - 对于获取对象但可能找不到它的函数,最好的 func 签名是什么?

标签 function go design-patterns null

关闭。这个问题是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 {
   ...
}

它的问题是:
  • 在许多情况下检查对象是否存在也意味着获取它。因此,我们为两次执行相同操作而付出代价(假设没有可用的缓存)。
  • 对“IsExist”的调用可以返回 true,但如果对象在被调用时被移除,“Find”可能会失败。在高并发应用程序中,它可能经常发生。这再次强制检查“查找”中的 nil 值。

  • 选项 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 值,指示是否找到它。

    在这种情况下,我的偏好是始终返回一个文字 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 。

    最后的选择是 panic (即其他语言中的“抛出异常”)。但实际上在所有情况下都应该避免这种情况,这绝对是这种功能的错误方法。我只是在这里提到它的完整性。 不要这样做
    func FindUserByID(id string) *User {
        /* Couldn't find the user so... */
        panic("Can't find the user!")
    }
    


  • 这是我对您的 6 个选项的具体批评(再次:我的观点)。
  • 选项 1:找不到时返回 nil。

    这是不直观的。我的建议是仅在零值(在您的情况下为 nil)本身有效时才执行此操作。它可能不适合用户。可能是为了计数。
  • 选项 2:返回异常

    您的示例实际上只返回一个错误,而不是异常( panic )。返回错误是一个完全有效的选项,但不要求助于检查错误字符串。请参阅我上面的讨论。
  • 选项 3:一功能查一检索

    这是不直观的,而且很活泼。如何使用这个繁琐的 API 并不明显,所以我会避免它。这也很有趣,因为无法保证在检查和检索之间没有创建新用户或删除现有用户。
  • 选项 4:更改名称以建议它返回 nil

    这将比选项 #1 略有改进,但不是惯用的 Go。有更好的选择。
  • 选项 5: IsEmpty() 方法

    这只是返回 bool 值的繁琐方式。更喜欢实际的 bool 值。
  • 选项 6:返回一个 bool 值

    这是一个有效的选项,在整个标准库中都可以看到。如果没有其他错误条件是可能的,这是合适的。见上面的评论。
  • 关于function - 对于获取对象但可能找不到它的函数,最好的 func 签名是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60828105/

    相关文章:

    oop - Golang 在具有私有(private)访问权限的结构中嵌入接口(interface)

    r - R 中的函数类似于月份的第一个字母的 month.abb?

    php - 有没有办法在 PHP 中获取引用函数的名称?

    c - C-函数定义具有用于参数的指针,在调用时传递的变量不是指针,而是编译和运行(有时)。怎么样?

    mysql - 使用go连接mysql数据库

    java - 在java中将两个不同的类消除为一个

    .net - 如何在几秒钟内使用 ReSharper 创建方法参数对象?

    c - 将函数指针分配给另一个函数内的函数

    go - 使用 go 从 g-suite 查询自定义属性

    Golang 上下文。上下文传播