返回 null 是坏设计吗?

我听到一些声音说,从方法中检查返回的 null 值是糟糕的设计。我想听听这样做的理由。

伪代码:

variable x = object.method()
if (x is null) do something
95243 次浏览

谁说这是糟糕的设计?

检查 null 是一种常见的做法,甚至是鼓励的做法,否则到处都有发生 NullReferenceException 的风险。优雅地处理错误比在不需要时抛出异常要好。

你好,

在无法创建新对象时返回 NULL 是许多 API 的标准做法。

我不知道为什么这个设计这么糟糕。

编辑: 这对于没有例外的语言来说是正确的,比如 C,它已经成为惯例很多年了。

高温

Avahappy,

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

从 CreateWidget ()方法返回 null 看起来很糟糕。

从 FindFooInBar ()方法返回 null 看起来不错。

对于某些场景,您希望一旦发生故障就立即注意到它。

针对 NULL 进行检查,并且在失败的情况下不断言(针对程序员错误)或抛出(针对用户或调用者错误) ,可能意味着后来的崩溃更难跟踪,因为没有找到最初的奇怪情况。

此外,忽略错误可能导致安全漏洞。也许空性来自于缓冲区被覆盖或类似的情况。现在,您是 没有崩溃,这意味着开发人员有机会在您的代码中执行。

这取决于你使用的语言。如果使用 C # 这样的语言,表示缺少值的惯用方法是返回 null,那么如果没有值,那么返回 null 是一个很好的设计。或者,在像哈斯克尔这样的语言中,在这种情况下习惯性地使用 Maybe monad,那么返回 null 将是一个糟糕的设计(如果可能的话)。

不返回 null 的基本原理是您不必检查它,因此您的代码不需要根据返回值返回 走另一条路。您可能想查看 空对象模式,它提供了关于这方面的更多信息。

例如,如果我要在 Java 中定义一个返回 Collection 的方法,我通常更愿意返回一个空集合(即 Collections.emptyList())而不是 null,因为它意味着我的客户端代码更干净;。

Collection<? extends Item> c = getItems(); // Will never return null.


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

比..:

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.
}
}

如果返回 null 在某种程度上是有意义的,那么返回 null 是可以的:

public String getEmployeeName(int id){ ..}

在这种情况下,如果 id 不对应于现有实体,返回 null 是有意义的,因为它允许您区分没有找到匹配的情况和合法错误。

人们可能会认为这是不好的,因为它可以被滥用为一个“特殊”返回值,表明一个错误条件,这是不太好的,有点像返回错误代码从一个函数,但混淆,因为用户必须检查返回空,而不是捕捉适当的异常,例如。

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

其他选择包括: 返回一些表示成功与否的值(或错误类型) ,但是如果你只需要一个表示成功/失败的布尔值,那么返回 null 表示失败,返回一个对象表示成功并不会降低正确性,然后返回 true/false 并通过参数获得对象。
另一种方法是使用异常来指示失败,但是在这里——实际上有更多的声音说这是一种糟糕的做法(因为使用异常可能很方便,但是有很多缺点)。
因此,我个人认为返回 null 表示出错,并在以后检查它(以实际了解是否成功)并没有什么不好的地方。此外,盲目地认为您的方法不会返回 NULL,然后根据它来编写代码,这可能会导致其他错误,有时很难发现错误(尽管在大多数情况下它只会使您的系统崩溃:) ,因为您迟早会引用0x000000000)。

在复杂程序的开发过程中,可能会出现意外的 null 函数,就像死代码一样,这种情况表明程序结构存在严重缺陷。

空函数或方法通常用作对象框架中可恢复函数或可重写方法的默认行为。

Null _ function@wikipedia

这不一定是一个糟糕的设计——因为有太多的设计决策,所以要看情况而定。

如果该方法的结果在正常使用中不会有很好的结果,返回 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 /* ... */ {
}

您认为返回 null 有哪些替代方案?

我看到两个案例:

  • FindAnItem (id)。如果找不到该项,应该执行哪些操作

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

  • 如果什么都没有找到,那么这个匹配(条件)应该返回什么?

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

我相信,返回 null 可能不如替代品好,因为它要求客户端记住检查 null,程序员忘记和代码

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

在 Java 中,抛出检查异常 RecordNotFoundException 允许编译器提醒客户端处理 case。

我发现,搜索返回空列表可以非常方便-只是填充显示与列表的所有内容,哦,它是空的,代码“只是工作”。

这当然取决于方法的用途... ... 有时,更好的选择是抛出异常。这要视情况而定。

这就是原因。

清洁代码作者: Robert Martin中,他写道,如果可以返回空数组,那么返回 null 就是糟糕的设计。既然期望的结果是一个数组,为什么不呢?它将使您能够在不带任何额外条件的情况下对结果进行迭代。如果它是一个整数,也许0就足够了,如果它是一个散列,空散列。等等。

前提是不要强制调用代码来立即处理问题。调用代码可能不想关注它们本身。这也是为什么在许多情况下,异常总比没有好。

例外al 情况是例外。

如果函数的目的是找到与给定对象相关联的属性,而该对象没有这样的属性,那么返回 null 可能是合适的。如果对象不存在,则引发异常可能更合适。如果函数的目的是返回一个属性列表,而没有要返回的属性,那么返回一个空列表是有意义的——您将返回所有的零属性。

返回 null 的良好用法:

  • 如果 null 是一个有效的 功能性的结果,例如: FindFirstObjectThatNeedsProcessing ()在未找到时可以返回 null,调用方应该相应地进行检查。

坏用途: 试图替换或隐藏例外情况,如:

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

在这些情况下,抛出异常更为恰当,因为:

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

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

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

有时候,返回 NULL 是正确的做法,但特别是当您处理不同类型的序列(数组、列表、字符串、等等)时,返回一个零长度的序列可能更好,因为它导致代码更短,希望更容易理解,同时不需要在 API 实现方面进行更多的编写。

如果代码类似于:

command = get_something_to_do()


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

如果有一个虚拟对象,它的 execute ()方法什么都不做,并且在适当的情况下返回这个对象而不是 Null,那么就不需要检查 Null 情况,只需要执行以下操作:

get_something_to_do().execute()

因此,这里的问题不在于检查 NULL 与异常之间,而在于调用方是否必须以不同的方式(无论以何种方式)处理特殊的非大小写。

如果你阅读了所有的答案,很明显,这个问题的答案取决于这种方法。

首先,当异常发生时(IOproblem 等) ,逻辑上会抛出异常。确切地说,特殊情况可能是另一个话题的特殊情况。.

当一个方法可能没有结果时,有两种情况:

  • 如果有可能返回一个 中性价值,这样做
    空枚举数、字符串等都是很好的例子
  • 如果不存在这样的中性值,则为 应该返回 null
    如前所述,假定该方法可能没有结果,因此它不是异常,因此不应引发异常。中立值是不可能的(例如: 0不是特别中立的结果,这取决于程序)

直到我们有一个正式的方法来表示一个函数可以或不可以返回 null,我尝试用一个变数命名原则来表示。
就像您对预期会失败的方法有 尝试某事()约定一样,当方法返回一个中立的结果而不是 null 时,我通常将方法命名为 安全

我对这个名字还不是很满意,但是想不出更好的名字了。所以我暂时就这么办。

让他们在事后调用另一个方法来判断前一个调用是否为空。嘿,是 对 JDBC 来说足够好了

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

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

  • 即席错误处理(而不是异常)
  • 语义模糊
  • slow instead of fast failing
  • 用计算机思维代替物体思维
  • 可变的和不完整的对象

请查看这篇博文以获得更详细的解释: http://www.yegor256.com/2014/05/13/why-null-is-bad.html。更多内容见我的书 优雅的物品,第4.1节。

我在这个地区有一个很好的会议

对于单项查询:

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

对于集合查询:

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

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

这个线程背后的基本思想是进行防御性编程。 有一系列不同的答复:

阿达姆斯基建议考虑空对象模式,对这个建议投赞成票。

迈克尔 · 瓦伦蒂还建议变数命名原则告诉开发者可能会发生什么。 如果这是 NULL 的原因,则 Zeroeption 建议正确使用 Exception。 还有其他人。

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

但我们有两个发展方案。

由开发人员“编写”的类: 作者

被另一个(可能)开发人员“消费”的类: 开发人员

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

如果开发人员不能这样做,那么 Class/method 就不是确定性的。 也就是说,如果获取对象的“方法调用”没有做它“宣传”的事情(例如 getEmployee) ,那么它就违反了契约。

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

因此,假设需要检查 NULL 或 NULL 对象(例如 if (雇员为 NullEmployee.ISVALID)) 并且这可能需要在一个 Employes 集合中发生,那么 null 对象方法是更好的方法。

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

抛出异常的 Author 正在删除开发人员用于测试对象有效性的选项, 这对于对象集合来说是非常糟糕的,并且会迫使开发人员进行异常处理 当分支他们的消费代码时。

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

因此,作为一个开发人员,我会从一个方法编写针对 NULL 的防御性程序。 如果作者给了我一个总是返回对象的契约(NULL OBJECT 总是这样) 并且该对象具有一个方法/属性,用于测试该对象的有效性, 那么我将使用该方法/属性继续使用该对象,否则该对象无效 我不能使用它。

底线是类/方法的作者必须提供机制 开发人员可以在他们的防御性编程中使用,也就是说,方法的更清晰的意图。

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

问候

GregJF

对于我的用例,我需要从方法返回一个 Map,然后寻找一个特定的键。但是如果我返回一个空的 Map,那么它将导致 NullPointerException,然后返回 null 与返回一个空的 Map 将会有很大的不同。 但是从 Java8开始,我们可以使用 可以选择