捕获空指针异常是代码异味吗?
Posted
技术标签:
【中文标题】捕获空指针异常是代码异味吗?【英文标题】:Is Catching a Null Pointer Exception a Code Smell? 【发布时间】:2011-02-04 21:21:27 【问题描述】:最近我的一个同事写了一些代码来捕获整个方法周围的空指针异常,并返回一个结果。我指出空指针可能有多种原因,因此我们将其更改为针对一个结果的防御性检查。
然而,捕捉 NullPointerException 对我来说似乎是错误的。在我看来,空指针异常是错误代码的结果,而不是系统中的预期异常。
是否存在捕获空指针异常有意义的情况?
【问题讨论】:
【参考方案1】:总的来说,我认为这是一种代码味道;在我看来,防御性检查更好。我会扩展它以涵盖大多数未经检查的异常,除了事件循环等想要捕获所有错误以进行报告/记录的情况。
我能想到的异常是调用一个无法修改的库,它可能会生成空指针异常以响应一些难以主动检查的断言失败。
【讨论】:
【参考方案2】:是的,捕获任何RuntimeException
几乎总是一种代码异味。 C2 Wiki 似乎同意了。
一个例外可能是一些特别防御性的代码片段,它们运行来自其他模块的几乎随机代码。此类防御结构的示例是 EDT、ThreadPools/Executors 和插件系统。
【讨论】:
你可以放心地离开那个“几乎”。 @BalusC 如果您在插件框架中调用代码并且不希望插件代码导致整个应用程序崩溃,那么您希望捕获任何未捕获的异常(包括运行时异常)的地方。一般来说,我会说它适用于要调用的代码被传递的代码(即侦听器),对吧? @Joseph:是的,这是有道理的。但是,在这种情况下,您宁愿捕捉Exception
甚至 Throwable
而不是特别是 RuntimeException
。
Spring 有一个很深的异常层次结构(都基于RuntimeException
),其中许多是非常容易捕获的(例如,基于重试的数据访问)。仅仅因为你没有有捕捉它,并不意味着你不应该捕捉它。【参考方案3】:
视情况而定。
这位同事的经验如何?他这样做是出于无知/懒惰,还是有真正的充分理由? (就像这是高于一切的主线程,永远不应该死?)
90% 的时间捕获运行时异常是错误的,99% 的时间捕获 NullPointerException 是错误的(如果原因是“我得到了很多...” 那么整个程序员是错误的,你应该注意他正在做的其余代码)
但在某些情况下捕获 NullPointerException 可能是可以接受的。
【讨论】:
我会对那些“某些情况”感到好奇。在我编写 Java 代码的这些年里,我还没有遇到过他们。 @BaluscC:考虑创建一个服务器,它在收到 NullPointerException 时的工作不是崩溃,而是向用户显示错误消息(就像任何体面的 servlet 容器一样??)如果它们在 NpE 时崩溃从用户那里抛出,他们不会做得很好。该服务器的主线程不应因程序员的错误而死。 是的,这当然是有道理的。然而,“捕获 NullPointerException”和“捕获异常”之间存在细微差别。 @BalusC 确实,这只是一个思考的例子。根据应用的性质,可能还有其他原因(但 99% 的情况是错误的) 亲爱的 OscarRyz,您能否举个例子说明 1% 的人在哪些情况下捕获 NullPointerException 是有意义的?【参考方案4】:确实如此。
大多数情况下,您的变量一开始就不应该为空。许多新语言都内置了对不可为空的引用类型的支持——即保证永远不会为空的类型。
对于你的传入值被允许为空的时候,你需要做一个检查。但是例外绝对是一种不好的方法。
if 语句可能需要三个指令来执行,并且是本地检查(意思是,您在需要保证的地方进行检查)。
另一方面,使用异常可能需要更多的指令——系统尝试查找方法,失败,在异常表中查找适当的异常处理程序,跳转到那里,执行处理程序,然后跳转再次。此外,检查可能是非本地的。如果你的代码是这样的:
try
return contacts.find("Mom").getEmail()
catch (NullPointerException e)
return null
您不知道 NPE 是在“getEmail”还是“find”中抛出的。
以更模糊的方式编写的非常非常常见的模式的技术更糟糕的解决方案?这不是排名,但它肯定闻起来很糟糕:/
【讨论】:
或联系人也可以为空。这就是为什么程序员应该知道他在做什么。 try / catch AnyException 肯定是不好的风格,不应该投入生产【参考方案5】:我试图保证我的接口的结果,但是如果某些库或某人的代码可以产生 null 作为结果,我希望保证捕获它可能是可行的。当然,一旦你抓住它,你会做什么取决于你。有时检查 null 是没有意义的,如果你发现它,你有一些其他的方法来解决问题,可能不是那么好,但可以完成工作。
我的意思是尽可能使用例外,这是一个非常好的语言功能。
【讨论】:
【参考方案6】:有时由于第三方库中的错误,我不得不捕获空指针异常。我们使用的库抛出了这个异常,我们对此无能为力。
在这种情况下,可以抓住它,否则就不行。
【讨论】:
我不认为这是可以接受的。该错误应报告给该库的维护人员,他们应尽快修复和重建。如果他们没有,那么显然是时候寻找另一个了。 @BalusC:我们应该拥有世界和平,为所有人提供免费冰淇淋,还有一匹小马...... @Derrick Turk 说了这么多。实际上,该错误位于主要库(JBPM)中 @Shervin:是的,但是那个库实际上已经崩溃了,它的内部状态现在可能不一致。一定要报告这个错误。 该错误已报告,并最终修复。虽然花了几个月的时间【参考方案7】:您应该捕获 NullPointerException(或者特别是任何 Throwable)的唯一位置是在某个***或系统边界,这样您的程序就不会完全崩溃并且可以恢复。例如,在您的 web.xml 中设置一个错误页面提供了一个包罗万象的功能,以便 Web 应用程序可以从异常中恢复并通知用户。
【讨论】:
【参考方案8】:如果您的方法调用外部接口(或 SOAP API)并且返回的值可能为 Null,则捕获 NullPointerException 会很有用。除此之外,捕获这些异常并没有太大的好处。
【讨论】:
如果返回值可能为空,您应该使用 if 语句。【参考方案9】:这真的取决于接口定义。非结构化 NPE 处理与捕获 Exception 或 Throwable 一样糟糕。
Null 用于识别未初始化的状态,而不是使用空字符串或 max_int 等。曾经我经常使用 null 的地方是回调对象不相关的地方。
我非常喜欢 Guice 提供的 @Nullable 注解。
http://code.google.com/docreader/#p=google-guice&s=google-guice&t=UseNullable
消除NullPointerExceptions 你的代码库,你必须遵守纪律 关于空引用。我们一直 通过以下和 执行一个简单的规则:
每个参数都是非空的,除非 明确规定。谷歌 集合库和 JSR-305 有 获取空值的简单 API 控制。 Preconditions.checkNotNull 如果为空,可用于快速失败 找到引用,@Nullable 可以 用于注释参数 允许空值。
Guice 默认禁止 null。它会 拒绝注入null,失败 而是 ProvisionException。如果 null 是 在你的班级允许的情况下,你可以 用注释字段或参数 @Nullable。 Guice 识别任何 @Nullable 注解,比如 edu.umd.cs.findbugs.annotations.Nullable 或 javax.annotation.Nullable。
【讨论】:
【参考方案10】:我能想到捕获NullPointerException
的唯一用途:
catch (NullPointerException)
ApplyPainfulElectricShockToProgrammer();
【讨论】:
【参考方案11】:很久以前我有一个用途。当通过键请求集合中的对象并且找不到对象时,一个特别愚蠢的库会抛出 NullPointerException。除了按键之外没有其他方法可以查看,也无法检查对象是否存在。
一段时间后,我们启动了供应商并开始修改库。现在库抛出了一个更好的异常(我的改变)并且有一个检查功能(别人的改变)。
当然,我总是会在 try 块中只得到一行。再多的话,我自己就会犯下糟糕的代码。
【讨论】:
【参考方案12】:是的,在 Java 中需要检查 NullPointerException。
当应用程序在需要对象的情况下尝试使用 null 时引发。其中包括:
调用空对象的实例方法。 访问或修改空对象的字段。 将 null 的长度视为一个数组。 访问或修改 null 的槽,就像它是一个数组一样。 将 null 当作 Throwable 值一样进行投掷。
应用程序应抛出此类的实例以指示空对象的其他非法用途。
读取文本文件(即 XML)时其他语言中的 NullPointerException,其记录尚未验证为正确的 ASCII 字符和记录格式。
【讨论】:
您为什么要在运行时处理它,而不是消除任何可能通过在开发时做正确的事情而引发的方式?【参考方案13】:捕获 NULL 指针异常实际上取决于上下文...应该努力避免严格的绝对规则...规则应该在上下文中应用 - 想要捕获此异常并将整个软件置于某种 STABLE 状态 - 做没有或几乎没有。所有这些编码规则都应该很好理解
此时,您将查看您的软件 AUDIT TRACE ... 您应该执行的操作并发现此异常的来源。
NULL 指针异常永远不会发生的想法必须是可验证的。首先进行静态分析...(如果有 3rd 方代码/组件进入,这会更难),然后使用相关工具进行详尽的状态空间搜索。
x
【讨论】:
【参考方案14】:捕获 NPE(实际上是任何 RTE)对于干净地终止基于 Swing-GUI 的应用程序是必要的。
edit :在这种情况下,它通常是通过 UncaughtExceptionHandler 完成的。
【讨论】:
【参考方案15】:搞笑
我刚刚发现了一些不应该在工作中做的事情:
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)
使值无效...
是的,它有效,但这不是一个很好的做法......
引发异常(尤其是填充堆栈跟踪)的成本远高于仅手动检查数据而没有异常...
【讨论】:
【参考方案16】:这个呢:
try
foo.x = bar;
catch (NullPointerException e)
// do whatever needs to be done
当 foo 可能为空但几乎从不为空时作为微优化?
想法是这样的:
显式 NULL 检查需要一条机器指令
另一方面,第二个版本中的 NULL 检查可以通过让 NULL 访问发生、捕获 SIGSEGV 并抛出 NullPointerException 来完成。如果对象不为 NULL,则这是免费的。
【讨论】:
【参考方案17】:这很糟糕,但它可以生成优化的字节码。
如果大多数时候整数 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
的情况会很昂贵,但对于not null
的情况,我们只需要 4 条指令 (0-7)。
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 编译器可以优化这个?
【讨论】:
【参考方案18】:如果程序员是初学者,他可能习惯于捕捉每一个阻止他获得最终输出的异常。代码审查者不应该对此感到满意。
捕获任何 RuntimeException 都是不好的。但是,如果确实有必要,那么代码中的注释将对将来处理该代码的程序员非常有帮助。如果你不能写一个合理的评论来捕捉它们,那么你必须避免它们。期间。
【讨论】:
以上是关于捕获空指针异常是代码异味吗?的主要内容,如果未能解决你的问题,请参考以下文章