我在这里寻找建议。我正在努力在不存在返回值或无法确定返回值的情况下从方法返回NULL还是返回空值更好。

以以下两种方法为例:

 string ReverseString(string stringToReverse) // takes a string and reverses it.
Person FindPerson(int personID)    // finds a Person with a matching personID.
 


ReverseString()中,我会说返回一个空字符串,因为返回类型是字符串,因此调用者期望这样做。同样,通过这种方式,调用方也不必检查是否返回了NULL。

FindPerson()中,返回NULL似乎更合适。无论是否返回NULL或空的Person对象(new Person()),调用者都必须在对其执行任何操作之前(例如调用UpdateName())检查一下Person对象是否为NULL或为空。那么,为什么不只在此处返回NULL,然后调用方只需要检查NULL。感谢您的帮助或见解。

评论

会有所不同。您也可能会争辩说,该方法应预先停止不正确的值,例如,将stringToReverse参数作为空或null传入。如果执行了该检查(抛出异常),它将始终返回非null的内容。

福勒·波伊亚-p。 496种基本模式-特殊情况(空对象)Bloch Effective Java,第2版项目43:返回空数组或集合,而不是null

@鲍里斯,我没看到你的评论。我实际上只是贴出特殊情况作为答案。

@Thomas我没有发现任何问题:)。至于这个问题,无论如何都是常识。

一个有趣的注释是在Oracle数据库中NULL和空字符串是相同的

#1 楼

在本问答中,StackOverflow对此主题进行了很好的讨论。在收视率最高的问题中,kronoz指出:

如果要表示
没有数据可用,通常最好返回null。
空对象表示已经有数据了。返回,而返回null另外,如果您
尝试访问对象中的成员,则返回null将导致null异常。对于
突出显示的错误代码-尝试访问什么都没有的成员
没有任何意义。访问空对象的成员不会失败
,这意味着错误可能不会被发现。

我个人而言,我喜欢为返回字符串的函数返回空字符串,以最大限度地减少需要放置到位。但是,您需要确保与您一起工作的小组遵循相同的惯例-否则将无法获得此决定的好处。
但是,正如SO回答中指出的那样,无效如果期望有对象,则应该返回该值,以便毫无疑问是否要返回数据。
最后,没有唯一的最佳方法。建立团队共识将最终推动团队的最佳实践。

评论


就个人而言,我喜欢为返回字符串的函数返回空字符串,以最大程度地减少需要放置的错误处理量。从不理解这个论点。空检查是什么,最多<10个字符?为什么我们这样行事呢?

–亚伦·麦克维(Aaron McIver)
2011年11月17日在18:44

没有人说这是回力劳动。但这是劳动。它在代码中添加了一种特殊情况,这意味着要测试一个分支。另外,它破坏了代码流。因为list_of_things()中的x {...}可以更快地找到,然后l = list_of_things();如果l!= null {...}

–布莱恩·奥克利(Bryan Oakley)
2011年11月17日在20:08

@Bryan:空值列表和空字符串之间有很大的区别。字符串很少代表字符列表。

–kevin cline
2011年11月18日下午4:45

@kevin克莱恩:当然。但是,对于字符串,无论是否为空,您都可能希望该字符串出现在日志中。必须检查它是否为空,以便您可以打印一个空字符串来混淆真正的意图。或类似于数据库字段,其中包含您要添加到网页的用户提供的内容。比起user.hometown == null {

–布莱恩·奥克利(Bryan Oakley)
2011年11月18日12:11



返回可选的。

–最好的祝福
19年11月18日在9:13

#2 楼

在我编写的所有代码中,都避免从函数返回null。我在“清理代码”中读过它。

使用null的问题是使用该界面的人不知道null是否是可能的结果,以及他们是否必须检查它,因为没有not null引用类型。在F#中,您可以返回option类型,可以是some(Person)none,因此对调用方来说,他们必须检查。

类似C#(反)模式是Try...方法:



 public bool TryFindPerson(int personId, out Person result);
 


现在我知道人们已经说过他们讨厌Try...模式,因为拥有输出参数破坏了纯函数的思想,但实际上与以下内容没有什么不同:

 class FindResult<T>
{
   public FindResult(bool found, T result)
   {
       this.Found = found;
       this.Result = result;
   }

   public bool Found { get; private set; }
   // Only valid if Found is true
   public T Result { get; private set;
}

public FindResult<Person> FindPerson(int personId);
 


...并且说实话,您可以假设每个.NET程序员都知道Try...模式,因为它是.NET框架内部使用的。这意味着他们不必阅读文档即可了解其功能,对我而言,这比坚持一些纯粹主义者的功能观点(了解resultout参数,而不是ref参数)更为重要。

所以我会选择TryFindPerson,因为您似乎表明找不到它是完全正常的。

另一方面,如果没有逻辑上的原因,呼叫者会提供一个不存在的personId,我可能会这样做:

 public Person GetPerson(int personId);
 


。 ..然后,如果它无效,我将抛出一个异常。 Get...前缀意味着调用者知道它应该成功。

评论


+1,如果您尚未这样做,我会参考清洁代码来回答这个问题。我喜欢这样的口头禅:“如果您的功能不能说出它的名字,那就抛出例外。” Muuuch比每十行左右检查一次null更好。

–格雷厄姆
2011年11月17日19:45

我已经放弃了任何有关人的知识的假设。

–CaffGeek
2011年11月17日在22:02

“使用null的问题在于使用接口的人不知道null是否可能是结果,以及他们是否必须检查它,因为没有不为null的引用类型。”由于引用类型可以为null,因此您是否不能始终假定null可能是结果?

–汤米·卡利尔(Tommy Carlier)
11-11-18在10:52



“使用null的问题是使用接口的人不知道null是否是可能的结果,以及他们是否必须检查它,[...]”作为API合同的一部分提及。

– ZsoltTörök
2011年11月18日在12:22

@Laiv-真的很天真。我们遵循模式的原因是使人们无需阅读文档即可轻松使用我们的东西。编写API就像设计用户界面一样,最流行的用户界面设计书之一是“不要让我思考”。尽可能使它无人阅读文档。

–斯科特·惠特洛克
16年5月6日10:00

#3 楼

您可以从企业应用程序体系结构的Paterns中尝试Martin Fowler的特例模式:


空值在面向对象程序中是笨拙的事情,因为它们
会破坏多态性。通常,您可以在给定类型的变量
引用上自由调用foo,而不必担心该项是确切类型还是子类。使用强类型语言,您甚至可以让编译器检查调用是否正确。但是,由于
变量可以包含null,因此您可能会在
上调用null消息,从而遇到运行时错误,这将为您提供一个友好的堆栈跟踪信息。

如果变量可能为空,则必须记住
用空测试代码将其包围,因此,如果存在空
,您将做正确的事情。通常在很多情况下正确的事情都是相同的,因此您结束
在很多地方编写了类似的代码-犯了代码重复的错误。

空是一个常见的例子这样的问题以及其他问题经常出现
。在数字系统中,您必须处理无穷大,对于诸如加法之类的东西,它打破了通常的实数不变性。我最早在商业软件中的经历
是与一位鲜为人知的公用事业客户(称为“乘员”)在一起的。所有这些都暗示着改变了
类型的常规行为。

而不是返回null或一些奇数值,而是返回一个Special Case
,该接口具有与呼叫者期望。


评论


我遇到了这种情况,但是只是在软件发布之后。我的测试数据从未触发过它,所以它有点出乎意料,调试起来很麻烦。尽管我没有用“特殊情况”模式来解决它,但是很高兴意识到这一点。

– samis
17 Mar 6 '17 at 18:05



#4 楼

我认为ReverseString()将返回相反的字符串,并且如果在IllegalArgumentException中传递则抛出Null。能够找到一些东西。

必须避免处理FindPerson()。在发明者中使用Null被称为“十亿美元的错误”!这是1965年null
引用的发明。那时,我正在设计第一个
全面类型系统,用于面向对象
语言(ALGOL W)的引用。

我的目标是确保对引用的所有使用绝对绝对安全,并由编译器自动执行检查。但是我
忍不住要插入空引用的诱惑,只是
,因为它很容易实现。

这导致了无数的错误,漏洞和系统崩溃,在过去的40年中可能造成十亿美元的痛苦和损害。

近年来,Microsoft使用了许多程序分析器(例如PREfix和PREfast
)来检查引用,并在出现警告的情况下发出警告,如果它们可能不是-空值。诸如Spec#之类的最新编程语言已经引入了对非null
引用的声明。这是我在1965年拒绝的解决方案。

Tony Hoare


#5 楼

返回一个选项。返回不同的无效值的所有好处(例如可以在集合中具有空值)而没有NullPointerException的风险。

评论


有趣。如何在Java中实现选项?在C ++中?

–乔治
2011年11月18日在12:17

@Giorgio,对于Java,来自Google的Guava库具有一个名为Optional的类。

– Otavio Macedo
2011年11月18日在16:30

对于C ++,有boost :: optional

– MSalters
2012年1月12日15:33

#6 楼

我看到了这种说法的两面,而且我意识到有些颇具影响力的声音(例如Fowler)主张不返回空值以保持代码干净,避免额外的错误处理块等。我倾向于支持返回null的支持。我发现调用方法有一个重要的区别,它在响应时没有任何数据,而在响应时我有一个空字符串。一个Person类,请考虑尝试查找该类实例的情况。如果您传递某些finder属性(例如ID),则客户端可以立即检查是否为null,以查看是否未找到任何值。这不一定是例外(因此不需要例外),但也应明确记录在案。是的,这需要客户端方面的一些严格要求,不,我认为这根本不是一件坏事。里面什么都没有。您是在其所有值(名称,地址,favouriteDrink)中放置空值,还是现在使用有效但空的对象填充空值?您的客户现在如何确定未找到实际的人?他们是否需要检查名称是否为空字符串而不是null?这样的事情难道真的会导致比我们只检查null并继续前进的情况更多或更多的代码混乱和条件语句吗?我可以同意这种说法,但是我发现这对大多数人来说是最有意义的(使代码更易于维护)。

评论


区别在于,“如果键不存在,FindPerson或GetPerson是否返回null或引发异常?”作为API的用户,我只是不知道,我必须检查文档。另一方面,bool TryGetPerson(Guid key,out Person person)不需要我检查文档,而且我知道不会抛出异常,这相当于一个非常特殊的情况。这就是我要在回答中指出的重点。

–斯科特·惠特洛克
2014-02-26 12:57



我认为人们太喜欢例外了。异常是不好的并且消耗资源。更不用说人们确实在不解决任何问题的情况下尝试捕获语句。异常是明显的迹象,表明根本上出错了。例如,空引用会引发异常,因为引用空对象显然是错误的。用它来检查是否有人发现是错误的。

–弗拉基米尔·科赞奇克(Vladimir Kocjancic)
2014年5月13日在9:17

@VladimirKocjancic:我完全同意:例外情况应用于例外情况,例如软件错误,硬件故障,系统配置错误。如果要计算部分函数(如findPerson),则绝对没有结果是绝对正常的。这不应导致异常。

–乔治
16年5月29日在8:12

#7 楼

如果您需要知道该项目是否存在,则返回null。否则,返回期望的数据类型。如果要返回项目列表,则尤其如此。通常可以安全地假设呼叫者想要一个列表,而他们想要遍历该列表。如果尝试遍历null而不是遍历一个空列表,许多(大多数?全部?)语言将失败。是否失败:

for thing in get_list_of_things() {
    do_something_clever(thing)
}


我应该为没有东西的情况添加特殊情况。

评论


正是由于这个原因,在期望使用列表或其他值组的情况下,我总是选择返回一个空列表。默认操作(在一个空列表中重复)几乎总是正确的,如果不是,则调用方可以显式检查该列表是否为空。

– TMN
11-11-17在19:03

#8 楼

它取决于方法的语义。您可以指定,您的方法仅接受“ Not null”。在Java中,您可以使用一些元数据注释来声明:



 string ReverseString(@NotNull String stringToReverse)
 


您应该以某种方式指定返回值。如果声明,则仅接受NotNull值,则势必会返回空字符串(如果输入也是空字符串)。

第二种情况在语义上有些复杂。如果此方法是通过主键返回某个人(并且该人应该存在),则更好的方法是引发异常。

 Person FindPerson(int personID)
 


如果您通过某个猜测的ID搜索人(在我看来这很奇怪),则最好将该方法声明为@Nullable。

#9 楼

当且仅当满足以下条件时,null才是最好的返回值:


在正常操作中应期望null结果。在某些合理的情况下,可能无法找到一个人,所以findPerson()返回null是可以的。但是,如果确实是意外失败(例如在名为saveMyImportantData()的函数中),则应该抛出异常。名称中有一个提示-例外是在特殊情况下!
null表示“未找到/没有价值”。如果您还有其他意思,请返回其他内容!很好的例子是返回Infinity或NaN的浮点运算-这些是具有特定含义的值,因此,如果您在此处返回null,将变得非常混乱。
该函数旨在返回单个值,例如findPerson()。如果设计用于返回集合,例如findAllOldPeople(),则最好使用空集合。一个必然的结果是,一个返回集合的函数永远不会返回null。

另外,请确保您记录了该函数可以返回null的事实。

如果遵循这些规则,则null几乎是无害的。请注意,如果您忘记检查是否为null,通常在此之后通常会立即获得NullPointerException,这通常是一个很容易修复的错误。这种快速失败的方法比拥有伪造的返回值(例如,空字符串)要好得多,伪造的返回值在系统中悄悄传播,可能破坏数据而不会引发异常。

最后,如果应用这些问题中列出的两个函数的规则:



FindPerson-如果找不到此人,则此函数应返回null
/>ReverseString-似乎通过传递字符串永远不会失败(因为所有字符串(包括空字符串)都可以颠倒)。因此,它永远不应返回null。如果出现问题(内存不足?),则应引发异常。


#10 楼

我建议尽可能使用Null Object模式。它简化了调用方法时的代码,并且您不会阅读诸如

if (someObject != null && someObject.someMethod () != whateverValue)

这样的丑陋代码,例如,如果某个方法返回了适合的对象集合某种模式,然后返回一个空集合比返回null更有意义,并且对该空集合进行迭代实际上不会造成性能损失。另一种情况是一种方法,该方法返回用于记录数据的类实例,在我看来,返回Null对象而不是null是更可取的,因为它不会强制用户始终检查返回的引用是否为null

在返回null很有意义的情况下(例如,调用findPerson ()方法),我会尝试至少提供一种如果对象存在则返回的方法(例如personExists (int personId)),另一个示例是Java中的containsKey ())。它使调用者的代码更整洁,因为您可以轻松地看到所需对象可能不可用(人员不存在,地图中不存在键)的可能性。在我看来,不断检查引用是否为Map会混淆代码。

#11 楼

人们已经对Maybe类型构造函数说了些什么:

除了不冒NPE的风险外,语义上的另一个好处。在函数世界中,我们处理纯函数,我们希望尝试使函数在应用时完全等于其返回值。考虑String.reverse()的情况:这是一个给定特定字符串表示该字符串反向版本的函数。我们知道该字符串存在,因为每个字符串都可以颠倒(字符串基本上是有序的字符集)。

现在,findCustomer(int id)怎么样?此功能代表具有给定ID的客户。这可能存在或可能不存在。但是请记住,函数只有一个返回值,所以您不能真正说“此函数返回给定ID的客户,否则返回null。返回一个空对象,它们会产生误导作用。我认为在这里,空对象也很容易引起误解,空客户就是客户,不是缺少客户,也不是要求ID的客户,因此返回它就是错误的。这不是我要的客户,但是您仍然声称您返回了一位客户。它的ID错误,很明显是个错误,它破坏了方法名称和签名所建议的合同,它需要客户代码来承担责任有关您的设计或阅读文档的信息。

这两个问题都可以通过null解决。突然,我们不是说“此功能代表具有给定ID的客户”。我们说的是“此函数代表具有给定ID的客户(如果存在)。现在,它的作用非常清晰明了。如果您要求具有不存在ID的客户,则会收到null。比Maybe Customer好吗?不仅是因为存在NPE风险,还因为Nothing的类型不仅是null,而是Nothing。它具有语义含义,专门表示“没有客户”,表示此类客户没有。

null的另一个问题当然是模棱两可的,是您没有找到客户,还是存在数据库连接错误,或者我只是不允许看到客户?

如果您有几个类似的错误情况要处理,则可以引发这些版本的异常,或者(我更喜欢这样)可以返回Maybe,表示出了问题或返回客户。它具有Maybe Customer的所有优点,同时还使您能够指定可能出问题的地方。

我要做的主要事情是捕获函数签名中的约定。不要假设事物,不要依赖转瞬即逝的约定,要明确。

#12 楼

我认为返回NULL,返回一些空结果(例如,空字符串或空列表)和引发异常之间是有区别的。

我通常采用以下方法。我认为函数f(v1,...,vn)调用是函数的应用

f : S x T1 x ... x Tn -> T


其中S表示“世界状态” T1,...,Tn是输入参数的类型,T是返回类型。

我首先尝试定义此函数。如果该函数是部分函数(即,有些输入值未定义),则我返回NULL来表明这一点。这是因为我希望计算正常终止,并告诉我所请求的功能未在给定的输入上定义。例如,使用空字符串作为返回值是模棱两可的,因为可能是在输入上定义了函数,并且空字符串是正确的结果。

我认为需要额外检查NULL指针在调用代码中是必须的,因为您要应用部分函数,​​并且被调用方法的任务是告诉您是否未为给定输入定义该函数。错误,这些错误不允许进行计算(即找不到任何答案)。例如,假设我有一个Customer类,并且想实现一个方法



 Customer findCustomer(String customerCode)
 


通过其代码在应用程序数据库中搜索客户。
在这种方法中,如果查询成功,我将


返回客户类的对象,如果查询未找到任何客户,则返回null。
如果不是po,则抛出异常可以连接到数据库。

额外检查是否为空,例如

 Customer customer = findCustomer("...");
if (customer != null && customer.getOrders() > 0)
{
    ...
}
 


是我正在做的事情的语义的一部分,我不会只是“跳过它们”以使代码读起来更好。我认为简化当前问题的语义只是为了简化代码不是一个好习惯。一些特殊的语法。

我还考虑使用Null Object模式(由Laf建议),只要我可以将类的null对象与所有其他对象区分开即可。

#13 楼

返回集合的方法应返回空,而其他方法则可以返回null,因为对于这些集合,可能会发生这样的情况,即您将拥有对象,但其中没有元素,因此调用方将仅针对大小而不是对两者进行验证。

#14 楼

对我来说,这里有两种情况。如果您要返回某种列表,则无论如何都应始终返回该列表,如果其中没有任何内容,则为空。

我唯一看到争论的情况是何时您要退货。我发现自己更喜欢在使情况快速失败的基础上在失败的情况下返回null。尝试使用null对象产生意外行为。我认为,如果他们忘记处理这种情况,则常规行事会更好。如果出了什么问题,我希望尽快例外。您不太可能通过这种方式发布错误。

#15 楼

1)如果函数的语义是它什么也不能返回,则调用者必须对此进行测试,否则他将例如

2)使函数在语义上始终返回(或抛出)某些东西是有好处的。

对于记录器,如果未定义记录器,则返回不记录的记录器是有意义的。返回集合时,几乎没有任何意义(除非在“足够低的级别”上,集合本身就是数据,而不是其中包含的内容)什么都不返回,因为空集是集合,而不是什么。

以一个人为例,我将具有两个功能,一个返回空值,第二个抛出该异常。 br />

#16 楼


如果应用程序期望数据可用但该数据不可用,则应返回NULL。例如,如果未找到城市,则基于邮政编码返回CITY的服务应返回null。然后,调用方可以决定处理null还是爆炸。
如果有两种可能,应返回空列表。有可用数据
或无可用数据。例如,如果人口大于特定数目,则返回
CITY的服务。如果没有满足给定条件的数据,则可以
返回空列表。


#17 楼

在面向对象的世界中,返回NULL是一个糟糕的设计。简而言之,使用NULL会导致:


临时错误处理(而不是异常)
歧义语义
缓慢而不是快速失败
计算机思维与对象思维
可变和不完整的对象

查看此博客文章以获取详细说明:http://www.yegor256.com/2014/05/13/why-null -is-bad.html