提问人:Lena Schimmel 提问时间:1/4/2009 最后编辑:Jason AllerLena Schimmel 更新时间:8/2/2020 访问量:9356
如何在 getter 链中跟踪 NullPointerException
How to trace a NullPointerException in a chain of getters
问:
如果我在这样的调用中得到 NullPointerException:
someObject.getSomething().getSomethingElse().
getAnotherThing().getYetAnotherObject().getValue();
我得到一个相当无用的异常文本,例如:
Exception in thread "main" java.lang.NullPointerException
at package.SomeClass.someMethod(SomeClass.java:12)
我发现很难找出哪个调用实际上返回了 null,经常发现自己将代码重构为如下所示:
Foo ret1 = someObject.getSomething();
Bar ret2 = ret1.getSomethingElse();
Baz ret3 = ret2.getAnotherThing();
Bam ret4 = ret3.getYetAnotherOject();
int ret5 = ret4.getValue();
然后等待更具描述性的 NullPointerException,它告诉我要查找哪一行。
你们中的一些人可能会争辩说,连接 getter 是不好的风格,无论如何都应该避免,但我的问题是:我可以在不更改代码的情况下找到错误吗?
提示:我正在使用 eclipse,我知道调试器是什么,但我无法弄清楚如何将其应用于问题。
我对答案的结论是:
有些答案告诉我,我不应该一个接一个地链接 getter,有些答案告诉我,如果我不听从这个建议,该如何调试我的代码。
我接受了一个答案,它教会了我何时链接 getters:
- 如果它们不能返回 null,请根据需要链接它们。无需检查 != null,无需担心 NullPointerExceptions(请注意,链接仍然违反了得墨忒耳定律,但我可以忍受这一点)
- 如果它们可能返回 null,则永远不要,永远不要链接它们,并检查每个可能返回 null 的 null 值
这使得任何关于实际调试的好建议都毫无用处。
答:
我通常不会像这样在有多个可为 null 的 getter 的情况下链式 getter。
如果您在 ide 中运行,则只需设置一个断点,然后依次对每个元素使用 ide 的“计算表达式”功能。
但是,当您从生产服务器日志中收到此错误消息时,您将挠头。因此,最好每行最多保留一个可为 null 的项目。
同时,我们可以梦想Groovy的安全导航操作员
您可能想参考有关避免 != null 的问题。
基本上,如果 null 是有效的响应,则必须检查它。如果没有,请断言它(如果可以的话)。但是,无论您做什么,请尝试尽量减少 null 是有效响应的情况,以及其他原因。
答案取决于你如何看待你的 getter 的(合同)。如果它们可能返回,您每次都应该检查返回值。如果 getter 不应该返回,则 getter 应该包含一个检查并抛出一个异常 (?) 而不是返回 ,你承诺永远不会返回。堆栈跟踪将指向确切的 getter。您甚至可以将 getter 发现的意外状态放在异常消息中。null
null
IllegalStateException
null
评论
在 IntelliJ IDEA 中,您可以设置异常断点。每当抛出指定的异常时,都会触发这些断点(可以将其范围限定为包或类)。
这样一来,应该很容易找到NPE的来源。
我假设,您可以在 netbeans 或 eclipse 中执行类似操作。
编辑:这是有关如何在eclipse中添加异常断点的解释
评论
早期失败也是一种选择。
在代码中可以返回 null 值的任何位置,请考虑引入对 null 返回值的检查。
public Foo getSomething()
{
Foo result;
...
if (result == null) {
throw new IllegalStateException("Something is missing");
}
return result;
}
评论
像这样的链式表达式调试 NullPointerExceptions(以及可能发生的大多数其他问题)是一件很痛苦的事情,所以我建议你尽量避免它。不过,您可能已经听够了,并且就像前面提到的海报一样,您可以在实际的 NullPointerException 上添加断点以查看它发生的位置。
在 eclipse(以及大多数 IDE)中,您还可以使用监视表达式来评估在调试器中运行的代码。您可以通过选择代码并使用 contet 菜单添加新手表来执行此操作。
如果控制返回 null 的方法,则还可以考虑 Null 对象模式(如果 null 是要返回的有效值)。
以下是使用 Eclipse 查找错误的方法。
首先,在行上设置一个断点:
someObject.getSomething().getSomethingElse().
getAnotherThing().getYetAnotherObject().getValue();
在调试模式下运行程序,允许调试器在命中行时切换到其透视图。
现在,突出显示“someObject”并按 CTRL+SHIFT+I(或右键单击并说“检查”)。
它是空的吗?你已经找到了你的NPE。它是否为非 null? 然后突出显示 someObject.getSomething()(包括括号)并检查它。 它是空的吗?继续往下走,找出NPE发生的位置,而不必更改代码。
将每个 getter 放在自己的行上并调试。单步执行 (F6) 每个方法以查找哪个调用返回 null
如果你发现自己经常写:
a.getB().getC().getD().getE();
这可能是代码异味,应避免使用。例如,您可以重构为哪些调用哪些调用哪些调用。(此示例可能对您的特定用例没有意义,但它是修复此代码异味的一种模式。a.getE()
b.getE()
c.getE()
d.getE()
另见得墨忒耳定律,其中说:
- 方法可以直接调用其类中的其他方法
- 方法可以直接在自己的字段上调用方法(但不能在字段的字段上调用方法)
- 当方法采用参数时,方法可以直接对这些参数调用方法。
- 当方法创建本地对象时,该方法可以对本地对象调用方法。
因此,不应有一连串的消息,例如.遵循这个“定律”除了使 NullPointerExceptions 更易于调试之外,还有更多好处。a.getB().getC().doSomething()
如果你不得不分拆行或进行精心的调试来发现问题,那么这通常是上帝告诉你你的代码没有足够早地检查空的方式。
如果你有一个方法或构造函数接受一个对象参数,而所讨论的对象/方法不能合理地处理该参数为null,那么只需检查并抛出一个NullPointerException。
我见过人们发明了“编码风格”规则来试图解决这个问题,例如“一行上不允许超过一个点”。但这只会鼓励编程在错误的地方发现错误。
评论
NPE 是 Java 中最无用的 Exception,句号。它似乎总是懒惰地实现,从不确切地说明是什么原因导致的,即使像“类 x.y.Z 为 null”这样简单,也会对调试此类情况有很大帮助。
无论如何,在这些情况下,我发现找到 NPE 投掷器的唯一好方法是以下类型的重构:
someObject.getSomething()
.getSomethingElse()
.getAnotherThing()
.getYetAnotherObject()
.getValue();
你有它,现在 NPE 指向正确的线,从而正确抛出实际 NPE 的方法。不像我想要的那样优雅,但它有效。
评论