【问题标题】:How do you diagnose which field/method caused a NullPointerException如何诊断导致 NullPointerException 的字段/方法
【发布时间】:2011-07-22 15:24:38
【问题描述】:

假设您的 Java 代码如下所示:

getPerson().getParent().getSiblings().first().getName()

然后你在该行上得到一个 NullPointerException。你怎么知道哪个是空的?您可以对其进行调试并检查每个调用,也可以暂时将链接拆分为多行。

但是,我记得不久前看到了一个开源项目,它可以运行,我认为它是一个 javaagent,它可以为 NullPointerException 提供更好的堆栈跟踪,它提供返回 null 结果的方法调用。不幸的是,我似乎找不到它。有谁知道这是怎么做的,或者它是什么工具?

【问题讨论】:

  • @Moonbeam 来自堆栈跟踪,他不会知道哪个方法导致它,因为它是按行的。
  • 堆栈跟踪显示,例如“SomeFile.java:11”,其中第 11 行是上面的行。问题是你怎么知道 getPerson() 返回 null 还是 getParent() 返回 null 等等
  • 我调试应用程序,设置断点,然后检查什么是空的。
  • 另外,我建议您考虑使用一些 EL 引擎,或 JXPath - 使用 XPath 语法操作 Java Bean 的实用工具。
  • @Ondra,如果你这样做,你不会失去静态类型检查和重构吗?

标签: java nullpointerexception javaagents


【解决方案1】:

有趣。

一个人可以有一个空的父母吗? (有性生殖的生物不是有两个父母吗?)

如果你是独生子怎么办? getSiblings() 应该返回什么? null 或者,更好的是,一个空集合?

如果你没有名字怎么办?斯汀或麦当娜是名字还是姓氏?如果首先,姓氏返回什么?

这里真正的问题是设计。您还没有充分考虑在 null 的情况下该怎么做。最好弄清楚这一点。

【讨论】:

  • 我现在做了这个例子,关键是当你有几个方法链接在一起并且其中一个返回 null 时。理想情况下,您的设计不存在空值问题(如您所说,返回一个空集合而不是空值)。想象一下,您正在调试遗留代码并且遇到了这个问题。
  • @duffymo 有时人们只是想把事情做好,而没有时间成为纯粹主义者。有时您有从某个地方反序列化的 POJO 对象,并且您希望它们没有空值(即休眠)。
  • @Adam,在这种情况下,代码不会关心哪个为空。
  • @Peter Lawry 如果他关心哪个是 null 我认为他应该使用 JSR 303 POJO 验证。
【解决方案2】:

恕我直言,当您不使用可能抛出 npe 的单行方法时,这是一种更好的风格。那么你就不会有这样的问题:) 即使将每个调用放在一行中(但像 method1()\n.method2() 这样的相同指令等等)也不是一个好主意。简单的代码重新格式化可以将它们放回一行。

如果方法可能返回 null,你应该每次都检查返回值。如果方法不应返回 null,则 getter 应包含检查并抛出异常(IllegalStateException?)而不是返回 null。

您可以使用 AspectJ 和加载时 veave(使用 javaagent)向现有代码添加此类检查。

例如:

/** Throw Error if a method for creating a Point returns null */
after () returning (Point p) : 
    call(Point+ SubPoint+.create(..)) {
    if (null == p) {
        String err = "Null Point constructed when this (" 
            + thisJoinPoint.getThis() 
            + ") called target (" 
            + thisJoinPoint.getTarget() 
            + ") at join point (" 
            + thisJoinPoint.getSignature() 
            + ") from source location (" 
            + thisJoinPoint.getSourceLocation()
            + ") with args ("
            + Arrays.asList(thisJoinPoint.getArgs())
            + ")";
        throw new Error(err);
    }
}

【讨论】:

  • 这行得通,但有时你认为某些东西永远不会返回 null,但确实如此。
  • 您可以添加到现有代码检查方法是否使用 AspectJ 和加载时 veave(使用 javaagent)返回 null。然后抛出例如带有详细消息的 IllegalStateException。
  • 如果您打算使用 AspectJ 并且只为 NPE 提供开销,为什么不在您的对象图上使用 JSR 303 验证。看我的回答。
  • @Adam Gent:添加注释通常意味着编译和代码更改。使用 AspectJ,您可以在加载时添加它。
  • @zacheusz 和大量开销使堆栈跟踪看起来很奇怪(我更喜欢编译时编织)。我觉得我在吃疯狂的药丸......什么时候发现和修复 NPE 变得如此困难,以至于需要更慢和奇异的方法。如果问题太大,为什么不切换到提供更好的 null 处理支持的 Scala 或 Groovy。或者只是将每个方法放在自己的行中。
【解决方案3】:

理想情况下,您可以将每个方法的返回值分配给一个变量,这样您就可以检查它们;无论如何,这就是编译器在内部对临时变量所做的事情。

如果您不想重写,您可以在该行设置一个断点,然后为每个函数添加一个监视(最简单),或者(如果您的 IDE 支持)一次性键入每个函数-a-time 进入“立即”窗口,看看会返回什么。

【讨论】:

  • 这行得通,但我可以发誓我看到了一个工具,您可以在其中使用 -javaagent:sometool.jar 运行您的应用程序,它会自动更改 NPE 的堆栈跟踪以显示返回的方法null,这比在调试器中设置断点和检查链的每个路径更容易
【解决方案4】:

我的观点是,这是一个简洁代码优于可维护代码的问题。链接函数可能会使代码看起来更干净,但可能会导致代码库难以长期维护。如果您将操作分开,您可能会拥有更长的功能,但追踪错误应该会容易得多。

【讨论】:

  • 我喜欢链接。流畅的界面就是炸弹。
【解决方案5】:

我认为有些人可能会让这比 OP 要求的复杂得多

您所要做的就是将每个链接的方法放在自己的行中。

getPerson()
.getParent()
.getSiblings()
.first()
.getName();

您将获得正确的行号,该行号将指示 NPE 是哪种方法(上一行返回 null)。

是的,您可以进行各种强大的 NPE 检查,但最终有时您只需要完成一些事情,而 null 可能不是预期的,所以不要用其他一些错误处理来粉饰它。

您还可以通过在对象及其子对象上添加validation annotations 来防止这种情况发生。验证对象,然后继续遍历对象树。这在过去对我来说效果很好。这将为您提供更好的自动可重用错误处理。另外,您正在记录哪些方法可以返回 null。

说它的糟糕设计支持链接我认为是不公平的,因为最流行的 Javascript (jQuery) 库之一一直在使用它(是的,它有时会返回 null/undefined 值)。

【讨论】:

  • 这是个好主意。验证注释可能非常有用,但您必须查看现有代码并将其添加到其中。
  • @zacheusz 我什至没有想到 OP 可能使用旧代码的可能性如此之大!
猜你喜欢
  • 1970-01-01
  • 2021-09-16
  • 1970-01-01
  • 1970-01-01
  • 2016-04-07
  • 1970-01-01
  • 2020-04-06
  • 2017-09-10
  • 1970-01-01
相关资源
最近更新 更多