从函数返回数据的最佳实践是什么?是返回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。我能想到几个原因:

将有大量的保护子句处理空引用if (f() != null)。

什么是空,它是一个公认的答案还是一个问题?null是特定对象的有效状态吗?(假设您是代码的客户端)。我的意思是所有引用类型都可以为空,但是它们应该为空吗?

当你的代码库不断增长时,使用null几乎总是会不时地产生一些意想不到的NullRef异常。

有一些解决方案,测试者-执行者模式或从函数式编程实现选项类型。

其他回答

请原谅我的伪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;
}

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

如果您打算表示没有可用的数据,返回null通常是最好的主意。

空对象表示已返回数据,而返回null则明确表示没有返回任何数据。

此外,如果试图访问对象中的成员,返回null将导致null异常,这对于突出显示有bug的代码很有用——试图访问没有任何成员的成员是没有意义的。访问空对象的成员不会失败,这意味着bug不会被发现。

我更喜欢null,因为它与空合并操作符(??)兼容。

(仅)在特定契约被破坏时抛出异常。 在您的特定示例中,请求基于已知Id的UserEntity,这将取决于丢失(删除)用户是否是预期情况。如果是,则返回null,但如果不是预期的情况,则抛出异常。 注意,如果函数名为UserEntity GetUserByName(字符串名),它可能不会抛出而是返回null。在这两种情况下,返回空的UserEntity都没有帮助。

对于字符串、数组和集合,情况通常是不同的。我记得MS的一些指导原则,方法应该接受null作为一个“空”列表,但返回零长度的集合而不是null。字符串也是一样。注意,你可以声明空数组:int[] arr = new int[0];

如果用户没有被找到的情况经常出现,你想要根据情况以各种方式处理(有时抛出异常,有时替换一个空用户),你也可以使用接近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();
}

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