如何在Java堆栈跟踪中获取行的列数

7

在某些语言中,我们可以在堆栈跟踪中获取一行的列号,但是在Java中,我们只有行号。

举个例子,在另一种语言中,我们可以有以下内容:

Error
    at <anonymous>:2:2
    at Object.InjectedScript._evaluateOn (<anonymous>:641:39)
    at Object.InjectedScript._evaluateAndWrap (<anonymous>:580:52)
    at Object.InjectedScript.evaluate (<anonymous>:495:21)"

虽然这可能不是一个好的例子,因为我是从浏览器控制台引起错误的,但您可以看到列号,这对于解决错误非常有帮助。

让我用Java举个例子(是的,名称已更改):

Caused by: java.lang.IllegalArgumentException: path was null
    at org.jboss.resteasy.specimpl.ResteasyUriBuilder.path(ResteasyUriBuilder.java:362)
    at enterprise.money.service(AbstractSomething.java:88)

这导致了包含88行的代码: URI uri = uriInfo.getBaseUriBuilder().path(objectA).path(objectB).build(); 根据我的堆栈跟踪,我无法检查哪个.path调用引起了异常。因此,我的问题是,是否有任何解决方案可以让我获取列的引用?
(为防止一些可能的替代答案,我们需要解决如何获取列号的问题,其他答案比如如何通过调试器步进或重构每个生成器模式等都不能回答这个问题)

类文件的标准行调试信息中不包含列号。解决您问题最简单的方法是在链式调用之间插入换行符。 - Holger
我以为这是不可能的,难道真的没有自动完成这个任务的工具吗?例如,我曾经使用过 EclEmma 来进行代码覆盖率测试,它会插入一些隐藏标记来显示代码的执行情况 - 这应该是可以做到的,不是吗? - Philipp Gayret
1
我猜你可以自己编写Java编译器。但除此之外,我认为你就没有什么办法了。 - Dawood ibn Kareem
好的,有自动化源代码格式化工具,可以配置来完成任务。但是为您搜索工具不是“stackoverflow”的范围。 - Holger
我一直梦想着有一个比行号更好的替代方案。不知道,也许是一些标记或表达式索引。除了解决这个问题之外,一旦行号和列号变得无关紧要,我们可以将源代码保存在某种规范化的格式中,并且通过IDE支持,每个开发人员都可以使用自己喜欢的格式样式。 - EndlosSchleife
3个回答

5

这是不可能的。

你可以通过按照以下方式格式化你的代码来绕过它:

URI uri = uriInfo
           .getBaseUriBuilder()
           .path(objectA)
           .path(objectB)
           .build();

在Oracle Java 8中,您可以编写以下代码:

```java

public static void main(String... ignored) {
    List<String> s = new ArrayList<>();
    s.add("hello");

    Predicate<? super String> predicate1 = (t) -> t.length() > 0;
    Predicate<? super String> predicate2 = (t) -> t.length() < 8;
    Predicate<? super String> predicate3 = null;

    List<String> collect = s.stream()
            .filter(predicate1) // 16
            .filter(predicate2) // 17
            .filter(predicate3) // 18
            .collect(Collectors.toList());
}

您将获得:
Exception in thread "main" java.lang.NullPointerException
    at java.util.Objects.requireNonNull(Objects.java:203)
    at java.util.stream.ReferencePipeline.filter(ReferencePipeline.java:161)
    at Example.main(Example.java:18)

针对Oracle JDK而言,这似乎只存在于Java 8中,而不是Java 7。


5
我不确定这一直有效。经常看到第一行受到责备。 - Peter Lawrey
3
这取决于编译器,即javac生成的代码会指责第一行,而Eclipse则按预期操作。 - Holger
希望您不介意我添加一个来自Oracle Java 8的工作示例。 - Peter Lawrey
2
@user1066946:Eclipse确实自带Java编译器。 - Holger
@Peter Lawrey:Eclipse根本不会计算serialVersionUID。这是“序列化”开发人员的设计错误,他们基于所有成员(包括private和甚至规范未涵盖名称的合成方法)来计算默认ID(并且不影响序列格式的语义)。 Eclipse过去曾自由使用其自己的命名方案来处理内部类相关的合成方法,并且该问题在为lambda实现创建的合成成员中重新出现。但这个问题不应归咎于Eclipse。 - Holger
显示剩余6条评论

3

如果要对Java进行逆向工程,那将会非常痛苦。理论上,您可以分析原始源代码,并找出哪些表达式可能会引发这样的异常。

如果您遇到此问题,很可能是您的代码行过于复杂/密集。大多数IDE都可以轻松地重构表达式以提取代码部分。这将使您更清楚地了解异常抛出的位置。


对于这个特定问题的另一个解决方案是使用带有@NotNull注释的方法,IDE可以检测哪些参数可能为null,但不应该为null。

URI uri = uriInfo.getBaseUriBuilder().path(objectA)
                                     .path(/*highlighted*/objectB).build();

集成开发环境可以警告您向一个不能接受null参数的方法传递变量。如果这样做,您将更早地发现这些错误,并且更容易修复它们。(IDE附带一些快速修复)

注意:通常情况下,如果API传递了不正确的null参数,应该抛出NullPointerException异常。然而,经常会不一致地抛出IllegalArgumentException异常。

我认为支持IllegalArgumentException的一个理由是:

  • IllegalArgumentException是您可能期望应用程序捕获的异常。而捕获NullPointerException看起来不太好。
  • 无效的null处理预计与其他不正确的参数(如长度为-1)相同。

在我看来,向不接受null的方法传递null是一种编程错误,而不是提供不正确输入的问题。


“IllegalArgumentException是你可能期望应用程序捕获的异常” - 我不同意。如果API被正确使用,就永远不会出现需要捕获IAE的情况。如果你得到了IAE,那么说明你错误地使用了API。这不是一个需要处理的错误情况。事实上,它意味着你应该更改调用代码以遵守API合同。 - Joffrey
@PeterLawrey,然而特定的实现可能期望不同范围的有效输入 - 那么该实现就违反了接口的契约。但是无论如何,如果接口没有清楚地指定它,而实现是自由的,那么IAE会告诉您该特定实现不接受它。问题在哪里? - Joffrey
1
IllegalArgumentExceptionNullPointerException都表示编程错误。如果参数为null,JRE类更倾向于使用NPE,并且Objects.requireNonNull(…)宣传了这种行为,因此我建议遵循这种模式。 - Holger
2
@user1066946 简而言之,使用多行和Java 8或Eclipse应该可以工作。 - Peter Lawrey
2
@Holger 都表示编程错误 -- 我同意,但在我看来,NPE是被调用代码中的编程错误,而IAE是调用代码中的编程错误。我同意JRE经常抛出NPE代替IAE,但我不同意这个决定。好吧,我想所有这些只是一种观点,不应该在这里讨论,而应该在这里:https://dev59.com/HHVD5IYBdhLWcg3wXaYd - Joffrey
显示剩余8条评论

3

您无法从堆栈跟踪中获取列。

您可以使用中间变量,这样每个方法调用都在不同的行上:

UriBuilder builder = uriInfo.getBaseUriBuilder();
builder = builder.path(objectA);
builder = builder.path(objectB);
URI uri = builder.build();

虽然有些麻烦,但这可以帮助暂时找到问题,然后再恢复原样。


1
奥卡姆剃刀发挥了最大的作用。 - hiergiltdiestfu
@hiergiltdiestfu 我同意 :) 好吧,这只是找出问题的一种方式。然后代码可以恢复到原始形式。NPE本来就是编程错误,适当的IllegalArgumentException不需要这样痛苦的解决方法。 - Joffrey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接