我有一些由于NullPointerException而失败的代码。在不存在该对象的对象上调用一个方法。
但是,这导致我考虑了解决此问题的最佳方法。我是否总是对空值进行防御性编码,以便将来为空指针异常证明代码,还是应该修复空值的原因,以免它在下游发生。
你怎么看?
我有一些由于NullPointerException而失败的代码。在不存在该对象的对象上调用一个方法。
但是,这导致我考虑了解决此问题的最佳方法。我是否总是对空值进行防御性编码,以便将来为空指针异常证明代码,还是应该修复空值的原因,以免它在下游发生。
你怎么看?
Answers:
如果null是您方法的合理输入参数,请修复该方法。如果不是,请修复呼叫者。“合理”是一个灵活的术语,因此我建议进行以下测试:该方法应如何处理空输入?如果找到多个答案,则null 不是合理的输入。
Precondition.checkNotNull(...)
。见stackoverflow.com/questions/3022319/...
IllegalArgumentException
当给定null时,您可能还想让您的方法抛出一个。这会向方法的调用者发出信号,指出该错误位于其代码中(而不是方法本身)。
不要使用null,请使用Optional
正如您已经指出的那样,null
Java 的最大问题之一是它可以在任何地方使用,或者至少可以用于所有引用类型。
不可能说是可能null
和什么不可能。
Java 8引入了一个更好的模式:Optional
。
来自Oracle的示例:
String version = "UNKNOWN";
if(computer != null) {
Soundcard soundcard = computer.getSoundcard();
if(soundcard != null) {
USB usb = soundcard.getUSB();
if(usb != null) {
version = usb.getVersion();
}
}
}
如果这些方法中的每一个都可能返回成功值或可能不返回成功值,则可以将API更改为Optional
s:
String name = computer.flatMap(Computer::getSoundcard)
.flatMap(Soundcard::getUSB)
.map(USB::getVersion)
.orElse("UNKNOWN");
通过在类型中显式编码可选性,您的接口将变得更好,并且代码将变得更简洁。
如果您未使用Java 8,则可以com.google.common.base.Optional
在Google Guava中查看。
番石榴团队的一个很好的解释: https //github.com/google/guava/wiki/UsingAndAvoidingNullExplained
有关null缺点的更一般的解释,并提供几种语言的示例:https : //www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/
@ Nonnull,@ Nullable
Java 8添加了这些注释,以帮助诸如IDE的代码检查工具发现问题。它们的效果相当有限。
检查什么时候有意义
不要写50%的代码检查是否为null,尤其是如果没有明智的代码可以使用 null
值。
另一方面,如果null
可以使用并且表示某些意思,请确保使用它。
最终,您显然无法null
从Java中删除。我强烈建议Optional
在可能的情况下替换该抽象,并null
在其他时候检查您可以对其进行合理的处理。
NullPointerException
?每次您在Java中调用实例方法时,A NullPointerException
确实会发生 。您几乎可以在每种方法中使用。throws NullPointerException
有多种方法可以解决此问题,将代码添加到代码if (obj != null) {}
中并不理想,因为它很杂乱,在维护周期后期读取代码时会增加噪音,并且容易出错,因为很容易忘记做此样板包装。
这取决于您是否希望代码继续安静地执行或失败。是null
错误还是预期条件。
什么是null
在每种定义和情况下,Null表示绝对缺少数据。数据库中的空值表示该列缺少值。从理论上讲,null String
与null 不相同String
,null int
与零不同。在实践中,“取决于”。空String
可以Null Object
为String类提供良好的实现,因为Integer
它取决于业务逻辑。
替代方法是:
该Null Object
模式。创建代表null
状态的对象实例,并使用对Null
实现的引用初始化对该类型的所有引用。这对于没有太多引用其他对象的简单值类型对象很有用,其他对象也可能是null
并且被期望是null
有效状态。
使用面向方面的工具来编织具有Null Checker
防止参数为空的方面的方法。这是针对null
出现错误的情况。
使用并assert()
不会比if (obj != null){}
噪音少多少而已。
使用合同执行工具,例如Java合同。与AspectJ之类的用例相同,但较新,使用注释而不是外部配置文件。方面和断言两全其美。
当已知传入数据null
并且需要用一些默认值替换传入数据时,1是理想的解决方案,这样上游使用者就不必处理所有空检查样板代码。根据已知的默认值进行检查也将更具表现力。
2、3和4只是方便的备用Exception生成器,可以替换NullPointerException
为更多有用的信息,这始终是改进。
到底
null
Java几乎在所有情况下都是逻辑错误。您应该始终努力消除根源NullPointerExceptions
。您应该努力不要将null
条件用作业务逻辑。if (x == null) { i = someDefault; }
只需对该默认对象实例进行初始分配即可。
null == null
(甚至在PHP中),但在数据库中,null != null
因为在数据库中,Null 表示未知值,而不是“无”。两个未知数不一定相等,而两个虚数却相等。
添加空检查可能会使测试成为问题。看这个伟大的演讲...
查看Google Tech讲座演讲:“干净的代码讲座-别找东西!” 他在24分钟左右谈到
http://www.youtube.com/watch?v=RlfLCWKxHJ0&list=PL693EFD059797C21E
偏执编程涉及到处添加空检查。起初似乎是个好主意,但是从测试的角度来看,它使您的空检查类型测试变得难以处理。
同样,当您为某些对象的存在创建前提条件时,例如
class House(Door door){
.. null check here and throw exception if Door is NULL
this.door = door
}
它会阻止您创建House,因为您将引发异常。假设您的测试用例正在创建模拟对象以测试除Door以外的其他东西,那么您不能执行此操作,因为需要Door
那些因创建Mock而遭受痛苦的人都清楚地知道这些烦恼。
总而言之,您的测试套件应该足够健壮,可以对门,房屋,屋顶或其他任何东西进行测试,而不必对此感到偏执。严重地,为测试中的特定对象添加空检查测试有多困难:)
您应该始终喜欢能正常工作的应用程序,因为您有多个测试证明它可以工作,而不是仅仅因为您在整个地方都有一堆前提条件的空检查而跳了起来才有效
tl; dr-检查是否有意外情况是好的null
s很好,但是应用程序尝试使它们变好是不好的。
细节
显然,在某些情况下 null
方法的有效输入或输出,而在其他情况下则无效。
规则1:
允许
null
参数或返回null
值的方法的javadoc 必须清楚地记录下来,并说明其null
含义。
规则2:
null
除非有充分的理由,否则不应将API方法指定为接受或返回。
给定一个方法相对于null
s 的明确约定,在不应该传递或返回a的地方是编程错误null
。
规则3:
应用程序不应尝试“弥补”编程错误。
如果方法检测到 null
到不应存在的方法,则不应尝试通过将其转换为其他方法来解决该问题。这只是向程序员隐藏了问题。相反,它应该允许NPE发生并引起故障,以便程序员可以找出根本原因并加以解决。希望在测试过程中会发现故障。如果没有,那就说明您的测试方法。
规则4:
在可行的情况下,编写代码以及早发现编程错误。
如果您的代码中存在导致大量NPE的错误,那么最困难的事情就是找出null
值的来源。简化诊断的一种方法是编写代码,以便null
尽快检测到。通常,您可以结合其他检查来执行此操作;例如
public setName(String name) {
// This also detects `null` as an (intended) side-effect
if (name.length() == 0) {
throw new IllegalArgumentException("empty name");
}
}
(显然,在某些情况下应根据实际情况调整规则3和4。例如(规则3),某些类型的应用程序必须在检测到可能的编程错误后尝试继续运行。(规则4)过多检查不良参数可能会导致错误。对性能的影响。)
我建议将方法固定为防御性的。例如:
String go(String s){
return s.toString();
}
应该更像这样:
String go(String s){
if(s == null){
return "";
}
return s.toString();
}
我意识到这绝对是微不足道的,但是如果调用者期望一个对象给他们一个默认对象,则不会导致传递空值。
new String()
。
null
s是最糟糕的选择,而不是抛出NPE。
到目前为止,以下有关null的一般规则对我有很大帮助:
如果数据来自您的控制之外,请系统检查空值并采取适当措施。这意味着要么抛出对函数有意义的异常(选中或未选中,只需确保异常的名称告诉您发生了什么。)。但是绝对不要在您的系统中散布可能潜在地带来意外的价值。
如果Null在数据模型的适当值范围内,请进行适当处理。
返回值时,尽量不要返回null。始终喜欢空列表,空字符串,空对象模式。当这是给定用例的最佳数据表示形式时,请将空值保留为返回值。
可能是最重要的……测试,再测试。在测试您的代码时,请不要像编码员一样对其进行测试,而应将其作为纳粹精神病患者的母体进行测试,并尝试想象各种各样的方法来折磨该代码。
在零点偏执方面,这往往会导致空虚,这通常会导致外观和代理将系统与外界接口,并在内部严格控制值并具有大量冗余。这里的外界几乎意味着我没有编写自己的所有代码。它的确耗费了运行时间,但是到目前为止,我很少需要通过创建代码的“空安全段”来优化它。我必须说,尽管我主要是为医疗保健创建长期运行的系统,但我最后想要的是接口子系统将您的碘过敏携带到CT扫描器崩溃,因为意外的空指针是因为另一个系统中的其他人从未意识到名称可能包含撇号或诸如耒耨的字符。
反正...我的2美分
我建议从功能语言中使用Option / Some / None模式。我不是Java专家,但是我在C#项目中大量使用了我自己的这种模式的实现,并且我确信它可以转换为Java世界。
该模式背后的想法是:如果逻辑上存在可能缺少某个值的情况(例如,通过id从数据库检索时),则提供一个Option [T]类型的对象,其中T是一个可能的值。如果缺少值,则返回没有类None [T]的值对象,如果返回的话-包含值的Some [T]对象返回。
在这种情况下,您必须处理价值缺失的可能性,并且如果您进行代码检查,则很容易发现错误处理的位置。要从C#语言实现中获得启发,请参阅我的bitbucket存储库 https://bitbucket.org/mikegirkin/optionsomenone
如果返回空值,并且在逻辑上等效于错误(例如,不存在文件或无法连接),则应引发异常或使用其他错误处理模式。同样,此背后的想法最终导致解决方案,当您必须处理价值缺失的情况时,可以轻松地在代码中找到错误处理的地方。
Apache Commons的lang库提供了一种处理空值的方法
如果传递的对象为null,则使用ObjectUtils类中的defaultIfNull方法可以返回默认值。
请勿使用Optional类型,除非它确实是可选的,否则通常最好将退出作为异常处理,除非您确实希望将null作为选项,而不是因为您经常编写错误的代码。
正如Google的文章所指出的那样,问题不是null,因为它有其用途。问题在于应该检查和处理空值,通常可以优雅地退出它们。
在程序的常规操作之外,有许多无效情况表示无效条件(无效的用户输入,数据库问题,网络故障,丢失的文件,损坏的数据),这是检查异常的目的,处理它们,甚至如果只是记录它。
异常处理允许在JVM中使用不同的操作优先级和特权,而不是像Optional这样的类型,这种异常和异常的发生具有特殊的意义,包括早期支持优先处理程序延迟加载到内存中,因为您不这样做。需要它一直挂在身边。
您无需在可能会发生的地方,可能访问不可靠数据服务的任何地方编写空处理程序,并且由于其中大多数都属于可以轻易抽象的几种通用模式,因此您只需要处理程序调用,但极少数情况除外。
因此,我想我的答案是将其包装在受检查的异常中并对其进行处理,或者解决不可靠的代码(如果它在您的能力范围内)。