如何在 getter 链中跟踪 NullPointerException

How to trace a NullPointerException in a chain of getters

提问人:Lena Schimmel 提问时间:1/4/2009 最后编辑:Jason AllerLena Schimmel 更新时间:8/2/2020 访问量:9356

问:

如果我在这样的调用中得到 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 值

这使得任何关于实际调试的好建议都毫无用处。

java 调试 nullpointerexception getter

评论

0赞 Lena Schimmel 1/5/2009
现在我看到这可能是两个独立的问题:1.使用链式 getter 时如何避免 NPE,2.如果我没有避免 NPE,我该如何追踪 probrem。我很高兴我对这两个问题都得到了很好的答案,但很难选择接受哪个答案。

答:

3赞 krosenvold 1/4/2009 #1

我通常不会像这样在有多个可为 null 的 getter 的情况下链式 getter。

如果您在 ide 中运行,则只需设置一个断点,然后依次对每个元素使用 ide 的“计算表达式”功能。

但是,当您从生产服务器日志中收到此错误消息时,您将挠头。因此,最好每行最多保留一个可为 null 的项目。

同时,我们可以梦想Groovy的安全导航操作员

1赞 cletus 1/4/2009 #2

您可能想参考有关避免 != null 的问题

基本上,如果 null 是有效的响应,则必须检查它。如果没有,请断言它(如果可以的话)。但是,无论您做什么,请尝试尽量减少 null 是有效响应的情况,以及其他原因。

10赞 Ronald Blaschke 1/4/2009 #3

答案取决于你如何看待你的 getter 的(合同)。如果它们可能返回,您每次都应该检查返回值。如果 getter 不应该返回,则 getter 应该包含一个检查并抛出一个异常 (?) 而不是返回 ,你承诺永远不会返回。堆栈跟踪将指向确切的 getter。您甚至可以将 getter 发现的意外状态放在异常消息中。nullnullIllegalStateExceptionnull

评论

0赞 MetroidFan2002 1/4/2009
Getter 永远不应该包含副作用逻辑,这会混淆代码并导致比原始问题更多的混乱。特别是因为要采取的流行测试范式是不测试 getter 和 setter,因为它们应该很简单。
0赞 Lena Schimmel 1/5/2009
@rblasch:我真的很喜欢这个答案,因为它指出了我最初的问题:有些方法可能会返回 null,而另一些方法可能不会。我并不总是知道这个“合同”的东西。
0赞 Lena Schimmel 1/5/2009
@MeteroidFan2002:我同意 getter 不应该包含副作用逻辑,但 rblasch 的建议在我看来不像是副作用。副作用是改变对象的内部状态,或类似的东西。检查它是否履行合同似乎是一个不错的主意。
0赞 Ronald Blaschke 1/5/2009
布莱恩说得完全正确。MetroidFan2002,请注意,我没有提出任何副作用,我们都同意这将是不好的。顺便说一句,同样的理由也适用于 setter,但我会在那里使用 IllegalArgumentException。如果所有访问器都必须是哑的,我认为公共字段的价值不大。
0赞 MetroidFan2002 1/7/2009
getter 和 setter 用于反射,这就是它们自动生成的原因。场不能很好地与反射相配合。如果可能的话,getter 和 setter 应该是哑巴。二传手可能会产生副作用,但将其放入您的吸气器中会引起比解决更多的混乱。
9赞 Mo. 1/4/2009 #4

在 IntelliJ IDEA 中,您可以设置异常断点。每当抛出指定的异常时,都会触发这些断点(可以将其范围限定为包或类)。

这样一来,应该很容易找到NPE的来源。

我假设,您可以在 netbeans 或 eclipse 中执行类似操作。

编辑:这是有关如何在eclipse中添加异常断点的解释

评论

1赞 Miserable Variable 1/5/2009
您也可以在 Eclipse 中设置异常断点。你不能按类/包来限定它们的范围,但在这种情况下这应该不是问题,因为需要激活断点,只有一个执行到达链式的 getter。
0赞 ewalshe 1/8/2009
仅当您的测试识别出每个可能的 NPE 的来源时,此技术才有用。在生产环境中,您无法在调试器中运行代码,因此任何 NPE 仍将生成无用的堆栈跟踪。
2赞 ewalshe 1/4/2009 #5

早期失败也是一种选择。

在代码中可以返回 null 值的任何位置,请考虑引入对 null 返回值的检查。

public Foo getSomething()
{
  Foo result;
  ...
  if (result == null) {
    throw new IllegalStateException("Something is missing");
  }
  return result;
}

评论

0赞 Neil Coffey 1/5/2009
同意 -- 但如果问题是空引用,则抛出 NullPointerException()!
0赞 ewalshe 1/8/2009
如果计算或数据库查找的结果为 null,当我期望出现不同情况时,IllegalStateException 是一个有效的选择。
0赞 willcodejavaforfood 1/4/2009 #6

像这样的链式表达式调试 NullPointerExceptions(以及可能发生的大多数其他问题)是一件很痛苦的事情,所以我建议你尽量避免它。不过,您可能已经听够了,并且就像前面提到的海报一样,您可以在实际的 NullPointerException 上添加断点以查看它发生的位置。

在 eclipse(以及大多数 IDE)中,您还可以使用监视表达式来评估在调试器中运行的代码。您可以通过选择代码并使用 contet 菜单添加新手表来执行此操作。

如果控制返回 null 的方法,则还可以考虑 Null 对象模式(如果 null 是要返回的有效值)。

2赞 MetroidFan2002 1/4/2009 #7

以下是使用 Eclipse 查找错误的方法。

首先,在行上设置一个断点:

someObject.getSomething().getSomethingElse().
getAnotherThing().getYetAnotherObject().getValue();

在调试模式下运行程序,允许调试器在命中行时切换到其透视图。

现在,突出显示“someObject”并按 CTRL+SHIFT+I(或右键单击并说“检查”)。

它是空的吗?你已经找到了你的NPE。它是否为非 null? 然后突出显示 someObject.getSomething()(包括括号)并检查它。 它是空的吗?继续往下走,找出NPE发生的位置,而不必更改代码。

0赞 Sathish 1/5/2009 #8

将每个 getter 放在自己的行上并调试。单步执行 (F6) 每个方法以查找哪个调用返回 null

5赞 Ross 1/5/2009 #9

如果你发现自己经常写:

a.getB().getC().getD().getE();

这可能是代码异味,应避免使用。例如,您可以重构为哪些调用哪些调用哪些调用。(此示例可能对您的特定用例没有意义,但它是修复此代码异味的一种模式。a.getE()b.getE()c.getE()d.getE()

另见得墨忒耳定律,其中说:

  • 方法可以直接调用其类中的其他方法
  • 方法可以直接在自己的字段上调用方法(但不能在字段的字段上调用方法)
  • 当方法采用参数时,方法可以直接对这些参数调用方法。
  • 当方法创建本地对象时,该方法可以对本地对象调用方法。

因此,不应有一连串的消息,例如.遵循这个“定律”除了使 NullPointerExceptions 更易于调试之外,还有更多好处。a.getB().getC().doSomething()

1赞 Neil Coffey 1/5/2009 #10

如果你不得不分拆行或进行精心的调试来发现问题,那么这通常是上帝告诉你你的代码没有足够早地检查空的方式。

如果你有一个方法或构造函数接受一个对象参数,而所讨论的对象/方法不能合理地处理该参数为null,那么只需检查并抛出一个NullPointerException。

我见过人们发明了“编码风格”规则来试图解决这个问题,例如“一行上不允许超过一个点”。但这只会鼓励编程在错误的地方发现错误。

评论

0赞 Miserable Variable 1/5/2009
我看不出如何在不将单个语句分解为多个语句的情况下进行任何早期检查,基本上遵循您不赞成的样式规则。
0赞 Neil Coffey 1/5/2009
这更多的是关于你在哪里分解它:我说的是在代码的实际逻辑开始之前,构造函数和方法开头的空检查,这样你就不会试图在方法/逻辑的中间分解行。
0赞 Miserable Variable 1/5/2009
恐怕我不明白。您能否澄清一下在这种情况下,在更改 someObject 的 getter 之前,您将使用哪些 null 检查?
16赞 Esko 1/5/2009 #11

NPE 是 Java 中最无用的 Exception,句号。它似乎总是懒惰地实现,从不确切地说明是什么原因导致的,即使像“类 x.y.Z 为 null”这样简单,也会对调试此类情况有很大帮助。

无论如何,在这些情况下,我发现找到 NPE 投掷器的唯一好方法是以下类型的重构:

someObject.getSomething()
          .getSomethingElse()
          .getAnotherThing()
          .getYetAnotherObject()
          .getValue();

你有它,现在 NPE 指向正确的线,从而正确抛出实际 NPE 的方法。不像我想要的那样优雅,但它有效。

评论

0赞 Lena Schimmel 1/6/2009
哇,从来没有尝试过,因为它看起来太容易了。我只是以为 Java 仍然会把它当作一行。我会试一试......
2赞 Esko 3/29/2017
自从我在 8 年前发布这篇文章以来,我注意到我所声称的并不总是正确的。我怀疑这与 Java 7 和 8 中的字节码更改有关,但我实际上懒得确认。
1赞 Lena Schimmel 6/8/2017
这么多年后回到这里的额外积分:)
0赞 DavidS 10/19/2018
并不总是正确的,但有时是真的?通常是真的吗?它似乎对我有用。