返回null是不好的设计吗?

142

我听到有些声音说从方法返回null值并进行检查是不好的设计。我希望听到一些原因。

伪代码:

variable x = object.method()
if (x is null) do something

14
详细说明一下:那些说这很糟糕的人在哪里?有链接吗? - jcollum
2
如果这个方法是你可以控制的,你可以编写单元测试来确保它永远不会返回 null。否则,我认为在调用之后检查是否为 null 并不是一种不好的做法;虽然该方法返回 null 可能是一种不好的做法,但你必须保护你的代码。 - BlackTigerX
9
仅仅因为没有数据返回而引发异常是非常烦人的。正常程序流程不应该抛出异常。 - Thorarin
4
@David:其实这就是我说的。如果一个方法应该返回数据,但没有返回任何数据,那也意味着出了问题。这不是正常的程序流程 :) - Thorarin
2
@Thorarin:“正常”的程序流程是一个相当灵活的概念:不是真正的论据基础。 - Tomislav Nakic-Alfirevic
显示剩余6条评论
24个回答

4

这不一定是一个糟糕的设计 - 就像许多设计决策一样,它取决于实际情况。

如果该方法的结果在正常使用中不会产生良好的结果,则返回null是可以接受的:

object x = GetObjectFromCache();   // return null if it's not in the cache

如果确实应该始终存在非空结果,那么抛出异常可能更好:

try {
   Controller c = GetController();    // the controller object is central to 
                                      //   the application. If we don't get one, 
                                      //   we're fubar

   // it's likely that it's OK to not have the try/catch since you won't 
   // be able to really handle the problem here
}
catch /* ... */ {
}

2
虽然你可以在那里记录诊断错误,然后再抛出异常。有时候首次故障数据捕获非常有帮助。 - djna
或者将异常包装在运行时异常中,将诊断信息放在消息字符串中。保持信息完整。 - Thorbjørn Ravn Andersen
现在(2018年),我不能再同意了,在这种情况下,我们应该更倾向于返回Optional<> - Piotr Müller

4

异常是为了处理特殊情况而存在的。

如果您的函数旨在查找与给定对象相关联的属性,但该对象没有此类属性,则返回null可能是适当的。如果对象不存在,则抛出异常可能更合适。如果函数旨在返回属性列表,并且没有要返回的属性,则返回空列表是有意义的 - 您正在返回所有零属性。


1
如果您尝试使用没有值的属性,那么会引发异常。(我假设您的规范说明该属性是可选的。)请编写一个单独的方法来检查其值是否已设置。 - Preston

3

如果返回null有意义的话,那么这样做是可以的。

public String getEmployeeName(int id){ ..}

在这种情况下,如果id与现有实体不对应,则返回null是有意义的,因为它允许你区分没有找到匹配项和合法错误的情况。
人们可能认为这是不好的,因为它可以被滥用作为指示错误条件的“特殊”返回值,这有点像从函数返回错误代码,但令人困惑,因为用户必须检查返回值是否为null,而不是捕获适当的异常,例如。
public Integer getId(...){
   try{ ... ; return id; }
   catch(Exception e){ return null;}
}

3
好的。如果出现错误情况,抛出异常。 - David Thornley
3
有一种情况需要做出例外。如果你请求一个不存在的员工姓名,那么显然出了些问题。请允许这个例外。 - Thorarin
我认为这有点字面意思,Thorarin-你可以对我的例子提出异议,但我相信你可以想象出某个函数返回匹配值或null(如果没有匹配项)。从哈希表中获取键的值怎么样?(一开始应该想到这个例子)。 - Steve B.
1
哈希表返回不存在的键的空值是不好的。这样做意味着您无法将空值存储为值,而不会产生不一致的代码。 - RHSeeger

2
让他们在调用后再调用另一个方法来确定前一个调用是否为null。;-) 嘿,这对于JDBC来说已经足够好了。

2
对于某些情况,您希望在故障发生时立即注意到。
检查是否为NULL并不断言(对于程序员错误)或抛出(对于用户或调用方错误),可以意味着以后的崩溃更难追踪,因为原始异常情况没有被发现。
此外,忽略错误可能导致安全漏洞。也许null-ness来自于缓冲区被覆盖或类似的原因。现在,您没有崩溃,这意味着攻击者有机会在您的代码中执行。

2

你认为除了返回null,还有哪些选择?

我看到两种情况:

  • findAnItem(id)。如果未找到该项应该怎么办?

在这种情况下,我们可以:返回Null或抛出(已检查的)异常(或者可能创建一个项目并返回它)。

  • listItemsMatching(criteria)。如果没有找到匹配项,应该返回什么?

在这种情况下,我们可以返回Null、返回空列表或抛出异常。

我认为返回null可能不如其他选择好,因为它要求客户端记得检查null,而程序员经常会忘记编码。

x = find();
x.getField();  // bang null pointer exception

在Java中,抛出一个受检异常RecordNotFoundException可以让编译器提醒客户端处理情况。

我发现返回空列表的搜索非常方便——只需用列表的所有内容填充显示,如果为空,代码“就能正常工作”。


3
在程序正常流程中抛出异常来表示可能发生的情况会导致非常丑陋的代码,例如 try { x = find() } catch (RecordNotFound e) { // do stuff }。 - quant_dev
返回空列表是一个好的解决方案,但仅当该方法处于可以返回列表的上下文中时。对于“findById”情况,您需要返回null。我不喜欢RecordNotFound异常。 - Thilo
这就是我们意见不同的关键所在。在我看来,x = find(); if ( x = null ) { work } else { do stuff } 和 try catch 之间的美感差别不大。如果必要的话,我愿意为了代码正确性而牺牲美感。在我的生活中,我经常遇到没有检查返回值的代码。 - djna

1
这篇文章的基本思想是进行防御性编程,即针对意外编写代码。关于此,有许多不同的回答:Adamski建议查看空对象模式,并因此被选为最佳建议;Michael Valenty也建议使用命名约定以告知开发人员可能出现的情况;ZeroConcept则建议在NULL的情况下适当使用异常等等。
如果我们制定了“规则”,即始终希望进行防御性编程,那么我们可以看到这些建议都是有效的。但是我们有两种开发场景:
1.由开发人员(作者)编写的类:作者
2.由其他开发人员(可能)使用的类:开发人员
无论一个类是否返回方法的返回值为NULL,开发人员都需要测试对象是否有效。如果开发人员不能做到这一点,那么该类/方法就不具有确定性。也就是说,如果“调用方法”获取对象时没有执行其“广告”的操作(例如getEmployee),那么它就违反了合同。作为一个类的作者,在创建方法时,我总是希望尽可能友好、防御性和确定性。
因此,无论是NULL还是NULL OBJECT(例如if(employee as NullEmployee.ISVALID)),都需要检查,并且可能需要在一组Employee中进行检查,因此null object方法是更好的方法。
但我也喜欢Michael Valenty的建议,即命名必须返回null的方法,例如getEmployeeOrNull。
抛出异常的作者正在消除开发人员测试对象有效性的选择,这在一组对象上非常糟糕,并迫使开发人员在分支使用代码时进行异常处理。
作为使用该类的开发人员,我希望作者给我避免或编写其类/方法可能面临的空情况的能力。
因此,作为开发人员,我将从方法中进行防御性编程。如果作者已经给我一个总是返回对象的合同(NULL OBJECT始终如此),并且该对象具有通过该方法/属性测试对象有效性的方法/属性,则我将使用该方法/属性继续使用该对象,否则该对象无效,我不能使用它。
底线是类/方法的作者必须提供机制,开发人员可以在其防御性编程中使用。也就是说,方法的意图更加清晰。
开发人员应始终使用防御性编程来测试从另一个类/方法返回的对象的有效性。
问候
GregJF

1
有时候,返回NULL是正确的做法,但特别是当你处理不同类型的序列(数组、列表、字符串等)时,最好返回一个零长度的序列,因为这会导致更短、更易理解的代码,而且在API实现者的编写上也不需要太多的工作。

1

嗯,这确实取决于方法的目的...有时候,更好的选择是抛出异常。这完全取决于具体情况。


0
如果代码类似于:

command = get_something_to_do()

if command:  # if not Null
    command.execute()

如果您有一个虚拟对象,其execute()方法什么也不做,并且在适当的情况下返回该对象而不是Null,则无需检查Null情况,而可以直接执行以下操作:
get_something_to_do().execute()

所以,这里的问题不在于检查 NULL 和异常之间的区别,而是在于调用者是否必须以不同的方式(无论如何)处理特殊的非情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接