捕获空指针exception代码的气味?
最近我的一个同事在一些代码中写了一个方法来捕获一个空指针exception,并返回一个单一的结果。 我指出了空指针可能有多less个原因,所以我们把它改为对一个结果的防御检查。
但是,捕获NullPointerException对我来说似乎是错误的。 在我看来,空指针exception是错误代码的结果,并不是系统中预期的exception。
是否有任何情况下,捕获空指针exception是有道理的?
是的,捕获任何RuntimeException
几乎总是一种代码味道。 C2维基似乎同意。
一个exception可能是一些特殊的防御性代码,它可以从其他模块中运行非常多的随机代码。 这样的防御结构的例子是EDT ,ThreadPools / Executors和插件系统。
我可以想到一个使用永远捕获一个NullPointerException
:
catch (NullPointerException) { ApplyPainfulElectricShockToProgrammer(); }
有时候由于第三方库中的一个错误,我不得不捕获空指针exception。 我们用的图书馆抛出了这个例外,我们无能为力。
在这种情况下,可以抓住它,否则不行。
这取决于。
这个同事有多经验? 他是因为无知还是懒惰而做这个事情呢,还是有一个很好的理由呢? (比如这是主线之上的一切,永远不应该死亡?)
90%的时间捕捉运行时exception是错误的,99%的捕捉NullPointerException是错误的(如果原因是“我得到了很多…”,那么整个程序员是错误的,你应该看看照顾他正在做的其他代码)
但是在某些情况下,捕获一个NullPointerException也是可以接受的。
一般来说,我认为这是一种代码味道; 在我看来,防守检查更好。 我会扩展到覆盖大多数未经检查的exception,除了事件循环等,想要捕获所有错误报告/日志logging。
我能想到的exception将会是对一个不能被修改的库的调用,它可能会产生一个空指针exception,以响应一些难以主动检查的断言失败。
滑稽
我刚刚发现了一些不该在工作中完成的事情:
public static boolean isValidDate(final String stringDateValue) { String exp = "^[0-9]{2}/[0-9]{2}/[0-9]{4}$"; boolean isValid = false; try { if (Pattern.matches(exp, stringDateValue)) { String[] dateArray = stringDateValue.split("/"); if (dateArray.length == 3) { GregorianCalendar gregorianCalendar = new GregorianCalendar(); int annee = new Integer(dateArray[2]).intValue(); int mois = new Integer(dateArray[1]).intValue(); int jour = new Integer(dateArray[0]).intValue(); gregorianCalendar = new GregorianCalendar(annee, mois - 1, jour); gregorianCalendar.setLenient(false); gregorianCalendar.get(GregorianCalendar.YEAR); gregorianCalendar.get(GregorianCalendar.MONTH); gregorianCalendar.get(GregorianCalendar.DAY_OF_MONTH); isValid = true; } } } catch (Exception e) { isValid = false; } return isValid; }
baaad 🙂
开发者希望日历提出这种例外:
java.lang.IllegalArgumentException: DAY_OF_MONTH at java.util.GregorianCalendar.computeTime(GregorianCalendar.java:2316) at java.util.Calendar.updateTime(Calendar.java:2260) at java.util.Calendar.complete(Calendar.java:1305) at java.util.Calendar.get(Calendar.java:1088)
使值失效
是的,但它不是一个很好的做法…
引发exception(特别是填充堆栈跟踪)比仅仅手动检查数据成本要高很多
这是不好的,但它可以产生优化的字节码。
如果Integer i
大部分时间不为null
,则检查整体性能。 检查本身需要3条指令(0-4)。 整个案例然后需要7条指令(0-14)。
public class IfNotNull { public Integer i; public String getIAsString() { if (i != null) { return i.toString(); } else { return ""; } } } public java.lang.String getIAsString(); Code: 0: aload_0 1: getfield #2 // Field i:Ljava/lang/Integer; 4: ifnull 15 7: aload_0 8: getfield #2 // Field i:Ljava/lang/Integer; 11: invokevirtual #3 // Method java/lang/Integer.toString:()Ljava/lang/String; 14: areturn // <- here we go 15: ldc #4 // String 17: areturn
遵循Python世界中常见的EAFP方法。 null
情况将是昂贵的,但我们只需要4个指令(0-7) not null
情况。
public class TryCatch { public Integer i; public String getIAsString() { try { return i.toString(); } catch (NullPointerException npe) { return ""; } } } public java.lang.String getIAsString(); Code: 0: aload_0 1: getfield #2 // Field i:Ljava/lang/Integer; 4: invokevirtual #3 // Method java/lang/Integer.toString:()Ljava/lang/String; 7: areturn // <- here we go 8: astore_1 9: ldc #5 // String a 11: areturn Exception table: from to target type 0 7 8 Class java/lang/NullPointerException
谁知道,如果JIT编译器可以优化这个?
必然是。
大多数情况下,你的variables不应该是null。 许多新的语言出来与内置的支持不可空的引用types – 也就是保证永远不会为空的types。
对于允许input值为空的时间,您需要进行检查。 但是例外情况确实是这样做的一个不好的方法。
if语句可能需要三条指令才能执行,并且是一个本地检查(也就是说,您需要在同一个地方进行检查,因为您需要保证)。
另一方面,使用exception可能需要更多的指令 – 系统尝试查找方法,失败,通过exception表查找适当的exception处理程序,跳转到那里,执行处理程序并再次跳转。 此外,支票可能是非本地的。 如果你的代码是这样的:
try return contacts.find("Mom").getEmail() catch (NullPointerException e) return null
你不知道NPE是在“getEmail”还是在“find”中被抛出。
一种技术上更糟糕的解决scheme,以一种更混乱的方式编写的非常常见的模式? 这不是排名,但肯定是臭的:/
唯一的地方,你应该捕捉一个NullPointerException(或者特别是,只是任何Throwable)是在一些顶级或系统的边界,以便您的程序不会完全崩溃,可以恢复。 例如,在web.xml中设置错误页面提供了一个全面的方法,以便Web应用程序可以从exception中恢复并通知用户。
捕获一个NULL指针exception真的取决于上下文…人们应该努力避免严格的绝对规则…规则应该在上下文中应用 – 想要捕获这个exception,并把整个软件置于稳定状态 – 什么都不做或几乎没有一无所有。 所有这些编码规则都应该很好理解
在这一点上,你看看你的软件AUDIT TRACE,你应该这样做,发现这个exception的来源。
一个NULL指针exception不会发生的想法必须是可validation的。 首先做一个静态分析…(如果第三方代码/组件进来,这更难),然后使用相关工具进行穷举状态空间search。
X
捕获NPE(实际上是任何RTE)可能需要干净地终止基于Swing-GUI的应用程序。
编辑:在这种情况下,它通常通过一个UncaughtExceptionHandler来完成。
那这个呢:
try { foo.x = bar; } catch (NullPointerException e) { // do whatever needs to be done }
作为一个微优化,当富可能是空的,但几乎从来没有?
这个想法是这样的:
-
明确的NULL检查需要一个机器指令
-
另一方面,第二个版本中的NULL检查可以通过让NULL访问发生,捕获SIGSEGV并抛出一个NullPointerException来完成。 如果对象不是NULL,则这是免费的。
很久以前我有一个使用。 当通过键询问集合中的对象并且未find对象时,特别愚蠢的库将抛出NullPointerException。 没有其他的方法来查找比通过关键和无法检查对象是否存在。
一段时间后,我们启动了供应商,并开始修改库。 现在,这个库会抛出一个更好的exception(我的改变),并有一个检查function(别人的改变)。
当然,我总是会在try块内完成一行。 更多的,我自己会犯有错误的代码。
我试图保证从我的接口的结果,但如果一些图书馆或某人的代码可以产生空的结果,我期待一个保证捕捉它可能是可行的。 当然,你做什么,一旦你赶上它取决于你。 有时候,检查null是没有意义的,如果你发现它,你还有其他的方法可以解决这个问题,但可能不是那么好,只是完成了工作。
我要说的是,你可以使用例外,它是一个非常好的语言function。
如果您的方法调用外部接口(或SOAP API),那么捕获NullPointerException可能很有用,并且返回的值可能为Null。 除此之外,捕捉这些例外并没有太大的好处。
这真的取决于接口定义。 非结构化的NPE处理和捕获Exception或Throwable一样糟糕。
空值对于识别未初始化的状态非常有用,而不是使用空string或max_int或其他。 一旦我经常使用null的地方是callback对象不相关的地方。
我非常喜欢Guice提供的@Nullable注释。
http://code.google.com/docreader/#p=google-guice&s=google-guice&t=UseNullable
为了消除代码库中的NullPointerException,你必须遵守空引用。 我们通过遵循和执行一个简单的规则取得了成功:
除非明确指定,否则每个参数都是非空的。 Google Collections库和JSR-305有简单的API来控制空值。 如果find空引用,则可以使用Preconditions.checkNotNull快速失败,并且可以使用@Nullable注释允许空值的参数。
Guice默认禁止为null。 它将拒绝注入null,而是以ProvisionException失败。 如果class级允许使用null,则可以使用@Nullable注释字段或参数。 Guice识别任何@Nullable注释,如edu.umd.cs.findbugs.annotations.Nullable或javax.annotation.Nullable。
是的,在Java中需要检查NullPointerException。
在应用程序试图在需要对象的情况下使用null时抛出。 这些包括:
调用空对象的实例方法。 访问或修改空对象的字段。 将null的长度视为一个数组。 访问或修改null的插槽,就好像它是一个数组。 抛出null,就像它是一个Throwable值一样。
应用程序应抛出此类的实例来指示其他非法使用null对象。
在读取文本文件(即XML)时其他语言的NullPointerException,其logging未被validation为正确的ASCII字符和logging格式。
如果程序员是初学者,他可能有一个习惯来捕捉每一个例外,从而阻止他获得最终的输出。 这不应该由代码审查人员受理。
捕获任何RuntimeException是不好的。 但是,如果真的有必要,那么代码中的评论对于将来编写代码的程序员来说真的很有帮助。 如果你不能写一个合理的评论来捕捉他们,那么你必须避免它们。 期。