【问题标题】:How to trace a NullPointerException in a chain of getters如何在 getter 链中跟踪 NullPointerException
【发布时间】:2010-09-29 11:32:27
【问题描述】:

如果我在这样的调用中收到 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,一些答案告诉我如果我不遵守该建议,如何调试我的代码。

我接受了一个答案,该答案教会了我何时链式吸气剂:

  • 如果它们不能返回 null,只要你喜欢就可以链接它们。无需检查 != null,无需担心 NullPointerExceptions(请注意,链接仍然违反 Demeter 法则,但我可以忍受
  • 如果它们可能返回 null,请永远不要链接它们,并检查每个可能返回 null 的 null 值

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

【问题讨论】:

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

标签: java debugging nullpointerexception getter


【解决方案1】:

NPE 是 Java 中最没用的异常。它似乎总是被懒惰地实现,并且从不确切说明是什么原因造成的,即使像“x.y.Z 类为空”这样简单,在调试此类情况时也会有很大帮助。

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

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

你有它,现在 NPE 指向正确的线,因此正确的方法抛出了实际的 NPE。不像我想要的那样优雅的解决方案,但它确实有效。

【讨论】:

  • 哇,从来没有尝试过,因为它看起来太容易了。我只是认为 Java 仍会将其视为一行。我会试一试...
  • 自从我在 8 年前发布此内容以来,我注意到我在此声明的内容并不总是正确的。我怀疑这与 Java 7 和 8 中的字节码更改有关,但实际上我懒得确认。
  • 这么多年回到这里的额外功劳:)
  • 并非总是如此,但有时是正确的?经常是真的?它似乎对我有用。
【解决方案2】:

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

【讨论】:

  • Getter 永远不应该包含副作用逻辑,这会混淆代码并导致比原始问题更多的混乱。特别是因为一个流行的测试范式是不测试 getter 和 setter,因为它们应该很简单。
  • @rblasch:我真的很喜欢这个答案,因为它指出了最初的问题:有些方法可能会返回 null,而其他方法可能不会。我并不总是知道这个“合同”的东西。
  • @MeteroidFan2002:我同意 getter 不应该包含副作用逻辑,但 rblasch 的建议对我来说似乎不是副作用。一个副作用是改变对象的内部状态,或者类似的东西。检查它是否履行合同似乎是一个相当不错的主意。
  • Brian 完全正确。 MetroidFan2002,请注意,我并没有提出任何副作用,我们都同意这是不好的。顺便说一句,同样的原理也适用于 setter,但我会在那里使用 IllegalArgumentException。如果所有访问者都必须是愚蠢的,那么我认为公共字段没有什么价值。
  • Getter 和 setter 用于反射,这就是它们自动生成的原因。字段不能很好地与反射配合使用。如果可能的话,getter 和 setter 应该是愚蠢的。 Setter 可能会产生副作用,但将它放在你的 getter 中会导致比它解决的更多的混乱。
【解决方案3】:

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

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

我想,你可以在 netbeans 或 eclipse 中做类似的事情。

编辑:Here 解释了如何在 eclipse 中添加异常断点

【讨论】:

  • 您也可以在 Eclipse 中设置异常断点。您不能通过类/包来确定它们的范围,但这在这种情况下应该不是问题,因为需要激活断点,只有一次执行到达链式 getter。
  • 这种技术只有在你的测试识别出每个可能的 NPE 的来源时才有用。在生产环境中,您无法在调试器中运行代码,因此任何 NPE 仍会生成无用的堆栈跟踪。
【解决方案4】:

如果你发现自己经常写作:

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

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

另见Law of Demeter,它说:

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

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

【讨论】:

    【解决方案5】:

    我一般不会像这样链接多个可空 getter 的 getter。

    如果您在 ide 中运行,您可以设置一个断点并在每个元素上连续使用 ide 的“评估表达式”功能。

    但是,当您从生产服务器日志中收到此错误消息时,您会摸不着头脑。所以最好每行最多保留一个可为空的项目。

    同时我们可以梦想groovy's safe navigation operator

    【讨论】:

      【解决方案6】:

      早期失败也是一种选择。

      在代码中任何可以返回空值的地方,考虑引入对空返回值的检查。

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

      【讨论】:

      • 同意 -- 但如果问题是空引用,则抛出 NullPointerException()!
      • 如果计算或数据库查找的结果为空,当我预期会有不同的结果时,IllegalStateException 是一个有效的选择。
      【解决方案7】:

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

      首先,在行上设置断点:

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

      在调试模式下运行程序,当行被命中时允许调试器切换到它的视角。

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

      它是空的吗?你找到了你的 NPE。它是非空的吗? 然后突出显示 someObject.getSomething() (包括括号)并检查它。 它是空的吗?等等。继续沿着链条找出您的 NPE 发生在哪里,而无需更改您的代码。

      【讨论】:

        【解决方案8】:

        您可以参考this question about avoiding != null

        基本上,如果 null 是一个有效的响应,你必须检查它。如果没有,请断言它(如果可以的话)。但无论您做什么,都应尽量减少 null 出于此原因而成为有效响应的情况。

        【讨论】:

          【解决方案9】:

          如果您不得不分道扬镳或进行复杂的调试以发现问题,那么这通常是上帝告诉您您的代码没有足够早地检查空值的方式.

          如果您有一个方法或构造函数接受一个对象参数,而相关的对象/方法无法合理地处理该参数为 null,那么只需检查并在那里抛出 NullPointerException。

          我看到人们发明了“编码风格”规则来尝试解决这个问题,例如“一行上不允许有多个点”。但这只会鼓励在错误位置发现错误的编程。

          【讨论】:

          • 我看不出如何在不将单个语句拆分为多个语句的情况下进行任何早期检查,基本上遵循您不赞成的样式规则。
          • 更多的是关于你在哪里打破它:我说的是在代码的实际逻辑开始之前在构造函数和方法的开头进行空检查,这样你就不会试图打破行在方法/逻辑的中间。
          • 恐怕我没有得到它。您能否澄清一下在更改 someObject 的 getter 之前在这种情况下您将使用哪些 null 检查?
          【解决方案10】:

          对于 NullPointerExceptions(以及可能发生的大多数其他问题),这样的链式表达式很难调试,因此我建议您尝试避免它。您可能已经听够了,就像之前提到的海报一样,您可以在实际的 NullPointerException 上添加断点以查看它发生的位置。

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

          如果您可以控制返回 null 的方法,如果 null 是要返回的有效值,您也可以考虑使用 Null Object 模式。

          【讨论】:

            【解决方案11】:

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2012-09-26
              • 1970-01-01
              • 2020-03-17
              • 1970-01-01
              • 2019-04-20
              • 2021-12-16
              • 1970-01-01
              相关资源
              最近更新 更多