使用IDEA进行debug的一个陷阱


事情的起因,是某天同事遇到了了一个问题,他写了如下这么一个类,为了方便理解我们把业务相关的信息都抹掉。

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class A {

    private List<String> unsortedList;

    @Override
    public String toString() {
        // 为了输出时方便查看List内容,将其排序后返回
        Collections.sort(unsortedList);

        return unsortedList.toString();
    }

    public List<String> getUnsortedList() {
        return unsortedList;
    }

    public void setUnsortedList(List<String> unsortedList) {
        this.unsortedList = unsortedList;
    }
}

在之后执行/调试的过程中他发现,这个类的表现很不稳定,unsortedList的结果在乱序和有序之间反复横跳,且自己在一步步调试过程中并没有涉及到给该数组排序的指令,这让他十分疑惑。
   经过观察他发现以下几个现象:

  • 在正常执行的过程中unsortedList与初始化时的顺序保持一致
  • 在调试过程中,如果不对A类进行断点观察,则unsortedList在使用时的顺序也与初始化时的一致
  • 但凡设了断点_观察_A,便会发现unsortedList被排序了

对此现象我们称之为薛定谔的排序(误。
   聪明的读者可能很快发现了,问题出在重写的toString方法上,但是当POJO类中充满大量业务相关的字段,或者toString中的操作并没有这么明显的时候,这个陷阱就很可能被忽略掉。
   实际上,在我们debug的时候,IDEA提供的展示值给我们带来了很多便利,而这个展示值就是通过调用toString方法实现的。
Alt text    通过查阅官方的使用文档我们也可以知道,通过设置Build, Execution, Deployment/Debugger/Data Views/Java,我们按需对不同类设置启用toString展示。同时在这一个设置窗口中,我们还能设置其他debug过程中的数据展示情况,如

Enable alternative view for Collection classes

这栏窗口允许设置更“人性化”的Collection classes展示方式(比如对于List的实现类,勾选该栏会展示List的大小,取消勾选会适应其他数据展示勾选项——如toString)。
   但事情到这还没结束,当我们在toString中打上断点,尝试进行“多余的”验证来解释debug的机制时,我们会发现始终无法进入toString方法,并且在IDEA下方会提示 Skipped breakpoint at debuggertrap.A:13 because it happened inside debugger evaluation。这行提示的意思是说,该断点发生在debugger的evaluation线程中,因此我们将其进行跳过,有趣的是在查找资料的过程中,我找到了这么一页讨论是关于eclipse是否该跳过evaluation阶段的toString断点的,对于是否跳过,参与讨论的人员都有认为confusing的点,显然IDEA的作者们替我们做了决定,将debugger进行evaluation时的toString进行了跳过(我没找到相关的设置,如果有大佬清楚其中的逻辑的话希望不吝赐教),这也确实造成了部分用户在使用时对逻辑产生了误判。
   关于IDEA中进行debug的toString隐式调用陷阱讲到这就算结束了。关于debug的原理部分我没有深入地进行研究,而关于其中的JDPA体系在网上有大量资料,我也就不献丑了,仅将官方资料放在👇,有兴趣的读者可以自行前往阅读。如果有时间进行探索将另开文章记录。

相关资料:

  1. IDEA关于debugger中的用户视图设置
  2. JPDA体系官方介绍
  3. 一篇JDPA体系的介绍
  4. 另一篇更深入的JDPA体系的介绍,含部分实现代码
  5. eclipse论坛中关于是否开启evaluation线程中的toString断点跳过的有意思的讨论