关于oop:返回null坏设计?

Is returning null bad design?

我听到一些声音说检查方法返回的空值是错误的设计。我想听听这方面的原因。

pseudocode:

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


不返回空值的基本原理是您不必检查它,因此您的代码不需要根据返回值遵循不同的路径。您可能需要签出空对象模式,该模式提供了有关此的更多信息。

例如,如果我在Java中定义了一个返回集合的方法,我通常宁愿返回一个空集合(即EDCOX1×0),而不是NULL,因为这意味着我的客户端代码更干净;

1
2
3
4
5
Collection<? extends Item> c = getItems(); // Will never return null.

for (Item item : c) { // Will not enter the loop if c is empty.
  // Process item.
}

…它比:

1
2
3
4
5
6
7
8
Collection<? extends Item> c = getItems(); // Could potentially return null.

// Two possible code paths now so harder to test.
if (c != null) {
  for (Item item : c) {
    // Process item.
  }
}


这就是原因。

在Robert Martin编写的干净代码中,他写道,当您可以返回空数组时,返回空值是不好的设计。既然预期的结果是一个数组,为什么不呢?它将使您能够在没有任何额外条件的情况下迭代结果。如果它是一个整数,也许0就足够了,如果它是一个散列,空散列。等。

前提是不要强迫调用代码立即处理问题。调用代码可能不想与它们有关。这也是为什么在许多情况下例外情况比零好的原因。


返回空值的良好用途:

  • 如果空值是有效的函数结果,例如:findfirstobjectthantneedsprocessing()如果找不到,则可以返回空值,调用方应相应地进行检查。

不良用途:试图替换或隐藏特殊情况,如:

  • catch(…)并返回空值
  • API依赖项初始化失败
  • 磁盘空间不足
  • 输入参数无效(编程错误,输入必须由调用方清除)

在这些情况下,引发异常更为充分,因为:

  • 空返回值不提供有意义的错误信息
  • 直接调用方很可能无法处理错误条件
  • 无法保证调用方正在检查空结果

但是,异常不应用于处理正常的程序操作条件,例如:

  • 无效的用户名/密码(或任何用户提供的输入)
  • 中断循环或作为非本地goto


是的,在面向对象的世界中,返回空值是一种糟糕的设计。简而言之,空用法导致:

  • 特殊错误处理(而不是异常)
  • 语义模糊
  • 慢而不是快失败
  • 计算机思维而不是物体思维
  • 可变和不完整对象

查看此博客文章了解详细解释:http://www.yegor256.com/2014/05/13/why-null-is-bad.html。在我的书《优雅的物体》第4.1节中有更多内容。


谁说这个设计不好?

检查空值是一种常见的实践,甚至鼓励这样做,否则到处都有发生空引用异常的风险。在不需要时,优雅地处理错误比抛出异常要好。


根据你到目前为止所说的,我认为没有足够的信息。

从CreateWidget()方法返回空值似乎不正确。

从findfooinbar()方法返回空值似乎很好。


它的发明者说这是一个十亿美元的错误!


这取决于你使用的语言。如果您使用的语言类似于C,表示缺少值的惯用方法是返回空值,那么如果没有值,则返回空值是一种很好的设计。或者,在诸如haskell这样的语言中,在这种情况下习惯性地使用mayboad,那么返回空值将是一种糟糕的设计(如果可能的话)。


如果你读了所有的答案,你就会明白这个问题的答案取决于方法的种类。

首先,当发生异常(ioproblem等)时,逻辑异常被抛出。当某件事是特殊的时候,可能是另一个话题的事情。

当一个方法可能没有结果时,有两个类别:

  • 如果可以返回中性值,请执行此操作。空的枚举表、字符串等是很好的例子
  • 如果不存在这样的中性值,则应返回空值。如前所述,假定该方法可能没有结果,因此它不是异常的,因此不应引发异常。中性值不可能(例如:0不是特别中性的结果,取决于程序)

直到我们有了一种正式的方法来表示一个函数可以或不能返回空值,我尝试用一种命名约定来表示空值。就像对预期会失败的方法有Trysomething()约定一样,当方法返回中性结果而不是空值时,我经常将我的方法命名为SafeSomething()。

我对这个名字还不太满意,但没能想出更好的名字。所以我现在就用它跑。


我在这方面有一个很好的会议

对于单项查询:

  • Create...返回一个新实例,或抛出
  • Get...返回预期的现有实例,或throw
  • GetOrCreate...返回一个现有实例,如果不存在则返回新实例,或者抛出
  • Find...返回一个存在的实例(如果存在),或者null返回一个存在的实例。

对于集合查询:

  • Get...总是返回一个集合,如果找不到匹配的[1]项,则该集合为空。

[1]给定了一些条件,显式或隐式,在函数名或参数中给出。


例外情况适用于特殊情况。

如果函数要查找与给定对象关联的属性,而该对象没有此类属性,则返回空值可能是合适的。如果对象不存在,则引发异常可能更合适。如果函数要返回一个属性列表,并且没有要返回的属性,那么返回一个空列表是有意义的——您返回的是所有零属性。


如果这样做在某种程度上有意义,则返回空值是可以的:

1
public String getEmployeeName(int id){ ..}

在这种情况下,如果ID与现有实体不对应,则返回空值是有意义的,因为它允许您区分未找到匹配项的情况与合法错误。

人们可能认为这是不好的,因为它可能被滥用为表示错误条件的"特殊"返回值,这不太好,有点像从函数返回错误代码,但由于用户必须检查返回值是否为空,而不是捕获适当的异常,例如。

1
2
3
4
public Integer getId(...){
   try{ ... ; return id; }
   catch(Exception e){ return null;}
}


这不一定是一个糟糕的设计-因为有这么多的设计决策,这取决于。

如果方法的结果在正常使用中不会有好的结果,则返回空值是可以的:

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

如果确实应该有一个非空的结果,那么最好抛出一个异常:

1
2
3
4
5
6
7
8
9
10
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 /* ... */ {
}


对于某些场景,您希望在失败发生时立即注意到它。

检查是否为空,在失败的情况下不断言(针对程序员错误)或抛出(针对用户或调用方错误),可能意味着以后的崩溃更难跟踪,因为没有找到原始的奇数情况。

此外,忽略错误可能导致安全漏洞。可能是因为缓冲区被覆盖之类的事实。现在,您没有崩溃,这意味着剥削者有机会在您的代码中执行。


对于返回空值,您看到了什么替代方法?

我看到两个案例:

  • 查找项(ID)。如果找不到该项目该怎么办

在这种情况下,我们可以:返回空值或抛出(选中的)异常(或者创建一个项并返回它)

  • listemsmatching(criteria)如果找不到任何内容,该返回什么?

在这种情况下,我们可以返回空值、返回空列表或引发异常。

我相信返回空值可能不如其他方法好,因为它要求客户端记住检查空值,程序员忘记并编码

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

在爪哇中,抛出一个已检查的异常,ReordOrdFunExtExpExt,允许编译器提醒客户端处理案例。

我发现返回空列表的搜索是非常方便的——只要用列表中的所有内容填充显示,哦,它是空的,代码"刚刚工作"。


有时,返回空值是正确的做法,但特别是当您处理不同类型的序列(数组、列表、字符串,您拥有什么)时,返回零长度序列可能会更好,因为它会导致代码更短,并且希望更易于理解,同时不会对API实现者进行太多的编写。


让他们在事实之后调用另一个方法,以确定上一个调用是否为空。嘿,这对JDBC来说已经足够好了。


这个线程背后的基本思想是进行防御编程。也就是说,用代码来防止意外。有一系列不同的答复:

Adamski建议查看空对象模式,并对该建议的回复进行投票。

MichaelValentity还建议使用命名约定来告诉开发人员可能需要什么。ZeroConcept建议正确使用异常,如果这是产生空值的原因。以及其他。

如果我们制定"规则",我们总是想做防御性编程,那么我们可以看到这些建议是有效的。

但我们有两个开发场景。

开发人员"编写"的类:作者

其他(可能)开发人员"消费"的类:开发人员

无论类是否为具有返回值的方法返回空值,开发人员需要测试对象是否有效。

如果开发人员不能这样做,那么该类/方法就不是确定性的。也就是说,如果用于获取对象的"方法调用"没有执行它"通告"的操作(例如getEmployee),那么它就违反了合同。

作为一个类的作者,我总是希望在创建一个方法时表现出友好和防御性(以及确定性)。

因此,如果需要检查空对象或空对象(例如if(employee as nullemployee.isvalid))。对于一个雇员集合可能需要这样做,那么空对象方法是更好的方法。

但我也喜欢迈克尔·瓦伦蒂的建议,即命名必须返回空值的方法,例如getEmployeeorNull。

引发异常的作者正在删除开发人员测试对象有效性的选择,这在对象集合上非常糟糕,并强制开发人员进行异常处理。当分支它们的消费代码时。

作为一个使用类的开发人员,我希望作者能够给我避免或为空情况编程的能力。他们的类/方法可能面临的问题。

因此,作为一个开发人员,我会对方法中的空值进行防御性的编程。如果作者给了我一个始终返回对象的协定(空对象总是这样做)并且该对象有一个方法/属性,用于测试该对象的有效性,然后我将使用该方法/属性继续使用该对象,否则该对象无效。我不能用它。

归根结底,类/方法的作者必须提供机制开发人员可以在防御编程中使用。也就是说,这种方法的意图更加明确。

开发人员应该始终使用防御性编程来测试返回的对象的有效性。来自另一个类/方法。

当做

格雷夫


Unintended null functions can arise during the development of a complex programs, and like dead code, such occurrences indicate serious flaws in program structures.

A null function or method is often used as the default behavior of a revectorable function or overrideable method in an object framework.

空的u函数@wikipedia


其他选项包括:返回指示成功与否的某个值(或错误类型),但如果您只需要指示成功/失败的布尔值,则返回失败的空值,成功的对象的正确性也不会降低,然后返回真/假并通过参数获取对象。另一种方法是使用异常来表示失败,但是这里——实际上有更多的声音,说这是一个糟糕的实践(因为使用异常可能很方便,但有很多缺点)。所以我个人并不认为返回空值有什么不好的地方,它表明出了问题,并在以后检查它(以实际知道您是否成功)。此外,盲目地认为方法不会返回空值,然后将代码基于空值,可能会导致其他错误,有时很难找到(尽管在大多数情况下,它只会使系统崩溃:),正如您迟早会引用0x00000000一样。


对于我的用例,我需要从方法返回一个映射,然后寻找一个特定的键。但是如果我返回一个空映射,那么它将导致nullpointerException,然后返回空映射而不是空映射的情况也不会有太大的不同。但是从Java8开始,我们可以使用可选的。以上就是引入可选概念的原因。


如果代码类似于:

1
2
3
4
command = get_something_to_do()

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

如果有一个伪对象,其execute()方法不做任何操作,并且在适当的情况下返回该对象而不是空对象,则不必检查空对象的大小写,而只需执行以下操作:

1
get_something_to_do().execute()

因此,这里的问题不是在检查空值和异常之间,而是在调用者必须以不同的方式(无论以何种方式)处理特殊的非情况之间。


当然,这取决于方法的目的…有时,更好的选择是抛出一个异常。这完全取决于具体情况。


G'Day.

在无法创建新对象时返回空值是许多API的标准实践。

为什么它的设计不好?我不知道。

编辑:这适用于没有例外的语言,如C语言,它已经成为多年的惯例。

高温高压

"快乐,