关闭。这个问题是基于意见的。它目前不接受答案。想改进这个问题?更新问题,以便可以通过编辑这篇文章用事实和引用来回答它。 3年前关闭。改进这个问题
我听到一些声音说从方法中检查返回的空值是不好的设计。我想听听一些原因。
伪代码:
variable x = object.method()
if (x is null) do something
不返回 null 背后的基本原理是您不必检查它,因此您的代码不需要根据返回值遵循不同的路径。您可能需要查看提供更多信息的 Null Object Pattern。
例如,如果我要在 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.
}
}
这就是原因。
在 Clean Code by Robert Martin 中,他写道,当您可以返回空数组时,返回 null 是不好的设计。既然预期的结果是一个数组,为什么不呢?它将使您能够在没有任何额外条件的情况下迭代结果。如果它是一个整数,也许 0 就足够了,如果它是一个散列,空散列。等等
前提是不强制调用代码立即处理问题。调用代码可能不想关心它们。这也是为什么在许多情况下异常优于 nil 的原因。
返回 null 的良好用途:
如果 null 是有效的函数结果,例如: FindFirstObjectThatNeedsProcessing() 如果未找到,则可以返回 null,调用者应进行相应检查。
不良用途:试图替换或隐藏异常情况,例如:
catch(...) 并返回 null
API 依赖初始化失败
磁盘空间不足
无效的输入参数(编程错误,输入必须由调用者清理)
ETC
在这些情况下,抛出异常更合适,因为:
空返回值不提供有意义的错误信息
直接调用者很可能无法处理错误情况
不能保证调用者正在检查空结果
但是,不应使用异常来处理正常的程序操作条件,例如:
无效的用户名/密码(或任何用户提供的输入)
打破循环或作为非本地 goto
boolean login(String,String)
看起来不错,AuthenticationContext createAuthContext(String,String) throws AuthenticationException
也不错
是的,在面向对象的世界中,返回 NULL 是一种糟糕的设计。简而言之,NULL 的使用会导致:
临时错误处理(而不是异常)
模棱两可的语义
缓慢而不是快速失败
计算机思维代替对象思维
可变和不完整的对象
查看此博文了解详细说明:http://www.yegor256.com/2014/05/13/why-null-is-bad.html。更多内容见我的书 Elegant Objects,第 4.1 节。
bool TryGetBlah(out blah)
或 FirstOrNull()
或 MatchOrFallback(T fallbackValue)
。
isEmpty()
),并且只有当它为真时才调用该方法。人们反对第二个,说它的性能更差——但正如 Unix 哲学所说,“重视人的时间而不是机器的时间”(即,与开发人员调试产生虚假错误的代码相比,性能稍慢一点所浪费的时间更少)。
谁说这是糟糕的设计?
检查空值是一种常见的做法,甚至受到鼓励,否则你会到处冒着 NullReferenceExceptions 的风险。与其在不需要时抛出异常,不如优雅地处理错误。
根据你到目前为止所说的,我认为没有足够的信息。
从 CreateWidget() 方法返回 null 似乎很糟糕。
从 FindFooInBar() 方法返回 null 似乎很好。
Create...
返回一个新实例,或抛出; Get...
返回预期的现有实例,或抛出; GetOrCreate...
返回现有实例,如果不存在则返回新实例,或抛出; Find...
返回现有实例(如果存在) 或 null
。 对于集合查询 - Get...
始终返回一个集合,如果未找到匹配项,则该集合为空。
这取决于您使用的语言。如果您使用像 C# 这样的语言,其中指示缺少值的惯用方式是返回 null,那么如果您没有值,则返回 null 是一个不错的设计。或者,在 Haskell 等语言中,在这种情况下习惯性地使用 Maybe monad,然后返回 null 将是一个糟糕的设计(如果它甚至可能的话)。
null
在 C# 和 Java 等语言中的定义经常被重载并在域中被赋予了一些含义。如果您在语言规范中查找 null
关键字,它仅表示“无效指针”。这可能在任何问题领域都没有任何意义。
null
还是“未初始化”null
如果您阅读了所有答案,就会清楚这个问题的答案取决于方法的种类。
首先,当发生异常情况(IOproblem 等)时,逻辑上会抛出异常。当某些事情确实很特别时,可能是针对不同主题的事情。
每当一种方法预期可能没有结果时,有两类:
如果可以返回中性值,请执行此操作。空枚举、字符串等都是很好的例子
如果不存在这样的中性值,则应返回 null。如前所述,假设该方法可能没有结果,因此它不是异常,因此不应抛出异常。中性值是不可能的(例如:0 不是特别中性的结果,取决于程序)
直到我们有一个正式的方式来表示一个函数可以或不能返回 null,我尝试有一个命名约定来表示。就像您对预期会失败的方法有 TrySomething() 约定一样,当方法返回中性结果而不是 null 时,我经常将方法命名为 SafeSomething()。
我对这个名字还不完全满意,但想不出更好的名字。所以我现在正在使用它。
我在这个领域有一个对我很有帮助的会议
对于单项查询:
Create... 返回一个新实例,或抛出
Get... 返回预期的现有实例,或抛出
GetOrCreate... 返回现有实例,如果不存在则返回新实例,或抛出
Find... 返回现有实例(如果存在)或 null
对于集合查询:
Get... 总是返回一个集合,如果没有找到匹配的 [1] 个项目,则该集合为空
[1] 给定一些标准,显式或隐式,在函数名称中或作为参数给出。
Get
,所以如果它不存在,那么它就是一个错误并且我抛出 - 我永远不需要检查返回值。如果我真的不知道它是否存在,我使用 Find
- 然后我需要检查返回值。
这不一定是一个糟糕的设计 - 就像许多设计决策一样,这取决于。
如果该方法的结果在正常使用中不会产生好的结果,则返回 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 /* ... */ {
}
Optional<>
例外是针对特殊情况。
如果您的函数旨在查找与给定对象关联的属性,并且该对象确实没有此类属性,则返回 null 可能是合适的。如果对象不存在,则抛出异常可能更合适。如果该函数旨在返回一个属性列表,并且没有要返回的属性,则返回一个空列表是有意义的——您将返回所有零属性。
如果这样做在某种程度上有意义,则可以返回 null:
public String getEmployeeName(int id){ ..}
在这种情况下,如果 id 与现有实体不对应,则返回 null 是有意义的,因为它允许您将未找到匹配项的情况与合法错误区分开来。
人们可能认为这很糟糕,因为它可能被滥用为指示错误条件的“特殊”返回值,这不太好,有点像从函数返回错误代码但令人困惑,因为用户必须检查返回null,而不是捕获适当的异常,例如
public Integer getId(...){
try{ ... ; return id; }
catch(Exception e){ return null;}
}
对于某些情况,您希望在故障发生时立即注意到它。
在失败案例中检查 NULL 并且不断言(针对程序员错误)或抛出(针对用户或调用者错误)可能意味着以后的崩溃更难追踪,因为没有找到原始的奇怪案例。
此外,忽略错误可能会导致安全漏洞。也许空值来自缓冲区被覆盖或类似情况的事实。现在,您没有崩溃,这意味着利用者有机会在您的代码中执行。
您认为返回 null 有哪些替代方法?
我看到两种情况:
查找AnItem(id)。如果找不到该项目该怎么办
在这种情况下,我们可以:返回 Null 或抛出(检查的)异常(或者创建一个项目并返回它)
listItemsMatching (criteria) 如果什么也没找到,应该返回什么?
在这种情况下,我们可以返回 Null、返回空列表或抛出异常。
我相信 return null 可能不如替代方案好,因为它要求客户端记住检查 null,程序员忘记和编码
x = find();
x.getField(); // bang null pointer exception
在 Java 中,抛出检查异常 RecordNotFoundException 允许编译器提醒客户端处理大小写。
我发现返回空列表的搜索非常方便 - 只需使用列表的所有内容填充显示,哦它是空的,代码“正常工作”。
好吧,这肯定取决于方法的目的……有时,更好的选择是抛出异常。这一切都取决于具体情况。
有时,返回 NULL 是正确的做法,但特别是当你处理不同类型的序列(数组、列表、字符串、你有什么)时,返回一个零长度的序列可能更好,因为它导致更短且希望更易于理解的代码,同时在 API 实现者方面不需要更多的编写。
该线程背后的基本思想是防御性编程。也就是说,针对意外情况编写代码。有一系列不同的回复:
Adamski 建议查看 Null Object Pattern,该回复被投票支持该建议。
Michael Valenty 还建议使用命名约定来告诉开发人员可能会发生什么。 ZeroConcept 建议正确使用 Exception,如果这是 NULL 的原因。和别的。
如果我们制定了我们总是想做防御性编程的“规则”,那么我们可以看到这些建议是有效的。
但是我们有两个开发场景。
由开发人员“编写”的类:作者
另一个(可能)开发人员“使用”的类:开发人员
无论一个类是否为具有返回值的方法返回 NULL,开发人员都需要测试该对象是否有效。
如果开发人员无法做到这一点,那么该类/方法就不是确定性的。也就是说,如果获取对象的“方法调用”没有做它“宣传”的事情(例如getEmployee),它就违反了合同。
作为课程的作者,我总是希望在创建方法时保持善良和防御性(和确定性)。
因此,鉴于需要检查 NULL 或 NULL OBJECT(例如 if(employee as NullEmployee.ISVALID))并且这可能需要在员工集合中发生,那么 null 对象方法是更好的方法。
但我也喜欢 Michael Valenty 关于命名必须返回 null 的方法的建议,例如 getEmployeeOrNull。
抛出异常的作者正在取消开发人员测试对象有效性的选择,这对对象集合非常不利,并在分支其消费代码时强制开发人员进行异常处理。
作为使用该类的开发人员,我希望作者让我能够避免或针对他们的类/方法可能面临的 null 情况进行编程。
因此,作为开发人员,我会针对方法中的 NULL 进行防御性编程。如果作者给了我一个始终返回对象的合同(NULL OBJECT 总是返回)并且该对象具有用于测试对象有效性的方法/属性,那么我将使用该方法/属性继续使用该对象,否则该对象无效,我无法使用它。
底线是类/方法的作者必须提供开发人员可以在其防御性编程中使用的机制。也就是说,该方法的意图更明确。
开发人员应始终使用防御性编程来测试从另一个类/方法返回的对象的有效性。
问候
格雷格
其他选项包括:返回一些指示成功与否(或错误类型)的值,但如果您只需要指示成功/失败的布尔值,则返回 null 表示失败,而返回一个对象表示成功不会不太正确,然后返回真/假并通过参数获取对象。其他方法会使用异常来指示失败,但在这里 - 实际上有更多的声音,说这是一种不好的做法(因为使用异常可能很方便,但有很多缺点)。因此,我个人认为返回 null 表示出现问题并稍后检查(实际知道您是否成功)并没有什么不好的地方。此外,盲目地认为您的方法不会返回 NULL,然后将您的代码基于它,可能会导致其他有时很难找到的错误(尽管在大多数情况下它只会使您的系统崩溃:),正如您将参考0x00000000 迟早)。
在复杂程序的开发过程中可能会出现意外的空函数,就像死代码一样,这种情况表明程序结构存在严重缺陷。空函数或方法通常用作对象框架中可重新向量函数或可覆盖方法的默认行为。
如果代码是这样的:
command = get_something_to_do()
if command: # if not Null
command.execute()
如果您有一个虚拟对象,其 execute() 方法什么都不做,并且在适当的情况下返回它而不是 Null,则您不必检查 Null 情况,而只需执行以下操作:
get_something_to_do().execute()
因此,这里的问题不在于检查 NULL 与异常之间,而是在于调用者必须以不同方式(以任何方式)处理特殊的非情况。
对于我的用例,我需要从方法返回一个 Map,然后寻找一个特定的键。但是,如果我返回一个空 Map,那么它会导致 NullPointerException,然后返回 null 而不是一个空 Map 不会有太大不同。但从 Java8 开始,我们可以使用 Optional。以上就是引入 Optional 概念的原因。
天,
当您无法创建新对象时返回 NULL 是许多 API 的标准做法。
为什么这是糟糕的设计,我不知道。
编辑:这适用于没有例外的语言,例如多年来一直是惯例的 C。
高温高压
'很高兴,
不定期副业成功案例分享
null
的变量是一个“容器”,它包含零个或一个指向对象的指针。 (这当然是 Haskell 的Maybe
在这些情况下明确建模的方式,这样做会更好。)