Function 什么';对于获取对象但可能找不到该对象的函数,什么是最好的func签名?

Function 什么';对于获取对象但可能找不到该对象的函数,什么是最好的func签名?,function,go,design-patterns,null,Function,Go,Design Patterns,Null,这是一个非常类似的问题,但重点是Go实现 假设有一个函数使用某个ID获取对象。该函数可能找不到该对象 比如: func FindUserByID(id int) *User { .... } 您应该如何处理找不到用户的情况 有许多模式和解决方案需要遵循。有些是常见的,有些在特定语言中是有利的 我需要选择一个适合Go(golang)的解决方案,因此我想查看所有选项,并获得关于最佳方法的反馈 Go有一些限制和一些可以帮助解决此问题的功能 备选案文1: 显而易见的解决方案是让“Find”函数

这是一个非常类似的问题,但重点是Go实现

假设有一个函数使用某个ID获取对象。该函数可能找不到该对象

比如:

func FindUserByID(id int) *User {
    ....
}
您应该如何处理找不到用户的情况

有许多模式和解决方案需要遵循。有些是常见的,有些在特定语言中是有利的

我需要选择一个适合Go(golang)的解决方案,因此我想查看所有选项,并获得关于最佳方法的反馈

Go有一些限制和一些可以帮助解决此问题的功能

备选案文1: 显而易见的解决方案是让“Find”函数返回nil,以防找不到对象。正如许多人所知,返回nil是不利的,它迫使调用方检查nil,使“nil”成为“notfound”的神奇值

它起作用了。这很简单

备选案文2: 返回异常-“NotFound”。
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”可能会失败。在高并发性应用程序中,这种情况经常发生。这再次强制检查“Find”中的nil值
  • 备选案文4: 更改“Find”的名称,表明它可能返回nil。这在.Net中很常见,名称可以是“TryFindByID”。 但很多人讨厌这种模式,我在其他任何地方都没见过。无论如何,它仍然使零值成为神奇的“不存在”标记

    备选案文5: 在某些语言(java、c++)中存在“可选”模式。这会产生一个清晰的签名,并帮助调用者理解她需要首先调用“isEmpty()”。 不幸的是,这在Go中不可用。github中有一些项目(如),但由于Go受到限制,并且不支持在不强制转换的情况下返回泛型类型,这意味着需要另一个编译步骤来为out对象创建可选结构,并在函数签名中使用它

    func FindUserByID(id int) OptionalUser {
       ....
    }
    
    func Foo() {
        optionalUser := FindUserByID(123)
        if optionalUser.IsEmpty() {
          ...
        }
        ...
    }
    
    
    但它依赖于第三方,增加了编译的复杂性。它使遵循此模式的结构数量加倍

    备选案文6: Go支持在函数中返回多个值。因此,如果对象存在,“Find”函数也可以返回bool值

    func FindUserByID(id int) (*User, bool) {
        ...
        if /*user not found*/ {
           return nil, false
        }
        ...
    }
    
    这似乎是围棋中的一个有利方法。例如,在Go中强制转换还返回一个bool值,表示操作是否成功

    我想知道什么是最好的方法,并就上述选项获得一些反馈


    编辑:将用户更改为指针(更好的例子)

    这个问题基本上是基于观点的,因此我不太愿意回答。但这里发生的事情已经够多了,我认为这需要一个答案,而不仅仅是评论

    您已经给出了6个选项,但实际上只有三个。您的6个选项中的大多数属于我的第一类。在底部,我将对你的每一个建议给出具体的评论

  • 返回用户,以及指示是否找到该用户的布尔值

    在这种情况下,我倾向于总是返回一个文本布尔值(但重点是:这是我的观点,而不是客观事实):

    在您的6个选项中,这对于任何代码的普通读者来说都是最明显的。对于是否可能返回nil值,或者是否必须查找某个复杂的API来执行两阶段查找等问题,它没有留下任何猜测

  • 返回用户和错误,可能包括“未找到”状态

    如果您可以选择其他错误状态(例如超时、数据库错误、格式错误的输入等),那么简单的布尔值是不够的,您必须返回错误(或恐慌,稍后会有更多)。在这种情况下,我的偏好(再次:我的观点)是使用错误来传达not found:

    func FindUserByID(id string) (*User, error)
    
    在这种情况下,您可以使用易于从来电者处检查的:

    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 */
    }
    
    不过,一般来说,哨兵错误不是最佳做法。通常通过接口传递错误类型更好,但这超出了本问题的范围

  • 返回用户,或惊慌失措

    最后一个选项是恐慌(即在其他语言中“抛出异常”)。但实际上在所有情况下都应该避免这种情况,对于这种函数来说,这绝对是错误的方法。我只是为了完整起见才在这里提到它不要这样做

    func FindUserByID(id string) *User {
        /* Couldn't find the user so... */
        panic("Can't find the user!")
    }
    

  • 以下是我对你的6个选择的具体评论(再次:我的观点)

    • 选项1:未找到时返回nil

      func FindUserByID(id int) *User {
          ...
          if /*user not found*/ {
             return nil
          }
          ...
      }
      
      这是不直观的。我的建议是,仅当零值(在您的情况下为nil)本身有效时才执行此操作。它可能不适合用户。可能是为了一个伯爵之类的东西

    • 选项2:返回异常

      您的示例实际上只返回一个错误,而不是异常(恐慌)。返回错误是一个完全有效的选项,但不要求助于检查错误字符串。见我上面的讨论

    • 选项3:一个要检查的函数,一个要检索的函数

      这是非直觉的,而且很活泼。如何使用这个笨重的API并不明显,所以我会避免使用它。它也很有活力,因为无法保证在检查和检索之间没有创建新用户,或者删除现有用户

    • <
      func FindUserByID(id string) *User {
          /* Couldn't find the user so... */
          panic("Can't find the user!")
      }