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... 返回预期的现有实例,或throwGetOrCreate... 返回一个现有实例,如果不存在则返回新实例,或者抛出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语言,它已经成为多年的惯例。
高温高压
"快乐,