从函数返回数据的最佳实践是什么?是返回Null对象好还是返回空对象好?为什么要选择一种而不是另一种呢?

考虑一下:

public UserEntity GetUserById(Guid userId)
{
     //Imagine some code here to access database.....

     //Check if data was returned and return a null if none found
     if (!DataExists)
        return null; 
        //Should I be doing this here instead? 
        //return new UserEntity();  
     else
        return existingUserEntity;
}

让我们假设在这个程序中存在有效的情况,即数据库中没有具有该GUID的用户信息。我认为在这种情况下抛出异常是不合适的??另外,我的印象是异常处理会损害性能。


当前回答

就我个人而言,我使用NULL。它清楚地表明没有数据要返回。但在某些情况下,空对象可能是有用的。

其他回答

如果用户没有被找到的情况经常出现,你想要根据情况以各种方式处理(有时抛出异常,有时替换一个空用户),你也可以使用接近f#的Option或Haskell的Maybe类型,它显式地将“无值”情况与“发现了一些东西!”数据库访问代码看起来像这样:

public Option<UserEntity> GetUserById(Guid userId)
{
 //Imagine some code here to access database.....

 //Check if data was returned and return a null if none found
 if (!DataExists)
    return Option<UserEntity>.Nothing; 
 else
    return Option.Just(existingUserEntity);
}

并且像这样使用:

Option<UserEntity> result = GetUserById(...);
if (result.IsNothing()) {
    // deal with it
} else {
    UserEntity value = result.GetValue();
}

不幸的是,每个人似乎都有自己喜欢的类型。

我个人返回该对象的默认实例。原因是我希望该方法返回0到多或0到1(取决于该方法的目的)。使用这种方法,它将是任何类型的错误状态的唯一原因是,如果方法不返回任何对象,并且总是期望返回(就一对多或单数返回而言)。

至于假设这是一个业务领域的问题——我只是没有从等式的那一边看到它。返回类型的规范化是一个有效的应用程序体系结构问题。至少,它是编码实践标准化的主题。我怀疑是否有业务用户会说“在场景X中,只给他们一个null”。

这取决于什么对你的案子最有意义。

返回null是否有意义?“不存在这样的用户”?

或者创建一个默认用户有意义吗?当您可以安全地假设如果用户不存在,则调用代码在请求时希望用户存在时,这是最有意义的。

或者,如果调用代码要求使用无效ID的用户,抛出异常(如“FileNotFound”)是否有意义?

然而,从分离关注点/SRP的角度来看,前两点更为正确。从技术上讲,第一个是最正确的(但只差一点点)——GetUserById应该只负责一件事——获取用户。通过返回其他内容来处理自己的“用户不存在”情况可能违反SRP。如果您确实选择抛出异常,则分隔为不同的check - bool DoesUserExist(id)是合适的。

根据下面大量的评论:如果这是一个api级别的设计问题,这个方法可以类似于“OpenFile”或“readwholfile”。我们正在从某个存储库中“打开”一个用户,并从结果数据中补充对象。在这种情况下,一个例外可能是合适的。也许不是,但也有可能。

所有的方法都是可以接受的——这取决于API/应用程序的上下文。

请原谅我的伪php/代码。

我认为这真的取决于结果的预期用途。

如果你想编辑/修改返回值并保存它,那么返回一个空对象。这样,您就可以使用相同的函数在新对象或现有对象上填充数据。

假设我有一个函数,它接受一个主键和一个数据数组,用数据填充行,然后将结果记录保存到db。因为我打算用我的数据填充对象,所以从getter返回一个空对象可能是一个巨大的优势。这样,我可以在两种情况下执行相同的操作。无论如何都要使用getter函数的结果。

例子:

function saveTheRow($prim_key, $data) {
    $row = getRowByPrimKey($prim_key);

    // Populate the data here

    $row->save();
}

在这里,我们可以看到相同的一系列操作操作了该类型的所有记录。

但是,如果返回值的最终目的是读取数据并对数据做一些事情,那么我将返回null。这样,我可以非常快速地确定是否没有返回数据,并向用户显示适当的消息。

通常,我将在检索数据的函数中捕获异常(因此我可以记录错误消息等),然后从捕获中直接返回null。对于最终用户来说,问题是什么通常并不重要,因此我发现最好将错误记录/处理直接封装在获取数据的函数中。如果你在任何大公司维护一个共享的代码库,这是特别有益的,因为你可以强制适当的错误记录/处理,即使是最懒的程序员。

例子:

function displayData($row_id) {
    // Logging of the error would happen in this function
    $row = getRow($row_id);
    if($row === null) {
        // Handle the error here
    }

    // Do stuff here with data
}

function getRow($row_id) {
 $row = null;
 try{
     if(!$db->connected()) {
   throw excpetion("Couldn't Connect");
  }

  $result = $db->query($some_query_using_row_id);

  if(count($result) == 0 ) {
   throw new exception("Couldn't find a record!");
  }

  $row = $db->nextRow();

 } catch (db_exception) {
  //Log db conn error, alert admin, etc...
  return null; // This way I know that null means an error occurred
 }
 return $row;
}

这是我的一般原则。到目前为止,它运行得很好。

You should be throwing an exception if it is an exceptional circumstance that you call that code with an invalid user ID. If it is NOT an exceptional circumstance, then what you are essentially doing is using a "getter" method to test whether a user exists or not. That is like trying to open a file to see if it exists or not (lets stick to c#/java here) instead of using the exists method, or trying to access dictionary elements and seeing if they exist by looking at the return value instead of using the "contains" method first.

因此,您很可能会使用诸如“exists”之类的额外方法来首先检查是否存在这样的用户。异常的性能绝对不是完全不使用它们的理由,除非您有真正的性能问题。