idea中的debug奇淫巧技

3,424 阅读7分钟

工欲善其事,必先利其器

开发过程中,遇到运行结果和预期的不一致的时候,最有效的方法就是debug了。当然随着开发经验的增加,不通过debug直接看代码有时也能定位问题,但是十分依赖经验和运气,本地debug还是最直接和彻底的解决方式。开发的IDE从eclipse转到idea也好几年了,工作中积累了一些debug常用的方法,做个总结,后续有新的姿势了继续来补充。

debug

idea中,debug非常简单,找到你要断点的地方,鼠标左键点击就会出现一个红色小球,这就打上断点了,用debug方式启动程序后,触发你要debug的方法,就会帮你停在这一行。

普通断点.png debug的时候,有各种变量和表达式,有些可能在代码里面并不直接出现,但是对你debug挺重要,这里可以用这个表达式计算器来帮你验证各种表达式的值,不需要自己人工计算。点这个计算器的小图标,然后输入你的表达式,idea就会帮你计算出当前表达式的值。

表达式计算器.png

条件断点

条件断点是debug中最常用的一个技巧,针对像for循环、递归等同一行代码在同一次触发中会反复进入的情况,如果没有条件断点,循环多少次就要在这个地方停多少次,十分麻烦,循环次数多了几乎在这个地方就无法断点。比例循环10000次,当前运行异常可以断定在6378次出了问题,如果不改代码,在循环中的某一行根本无法去打断点,打了之后就要手动放行6378次才能到,这时候只能通过修改代码,增加第6378次的判断,然后打再新增的代码上才能进去debug。有了条件之后,我们可以在断点上设置一个boolean表达式的条件,表达式为true的时候,才会在断点处停住。例如上面图片中的例子,i是0~4的循环,我们可以设置一个条件“i == 3”,这样只有在i == 3的时候,断点才会停住。设置条件断点的方法是,在断点上右键,弹出设置条件的面板,然后在条件中填入你的表达式,效果如下图

条件断点1.png

条件断点2.png 可以注意到,设置了条件断点后,断点icon的右下角会有一个小问号;运行起来后,并不是每次运行到这行代码都会停住,只有在满足i==3的时候,才会停住,这样我们就可以直接运行到出问题的那个循环。

单次断点

有些情况下,我们希望这个断点只生效1次就可以了,那我们就可以设置一下单次断点。 设置的方法是先打开“Breakpoints”(左边侧栏2个红点的图标),找到你的断点,然后勾选上“Remove once hit”就好。

单次断点.png

异常断点

这个也是开发中比较常用的一种断点方式。想象是否遇到过这样的场景:运行后报了空指针异常或者其他什么异常,但是当前堆栈信息不够判断是哪一行报的,比较传统的做法是,在方法开始的地方打一个断点,然后一步步跟着往下走,然后看看运行到哪一步会造成异常,如果方法比较长的话,这种方式比较费时。异常断点的意思是,并不直接在某一行设置断点,而是在某一种异常上设置断点,方法运行后,idea会帮你停在造成异常的这一行,这样的话,debug起来效率就高多了。 设置异常断点的方法是,先打开“Breakpoints”,点击左上角的“+”,选择到“Java Exception Breakpoints”,然后所搜你想断点的异常,如果是自定义异常,可以选择到“Project”。

异常断点1.png

异常断点2.png 设置完成后,并不会在某一行出现断点的标志,因为运行之前idea也不知道哪一行会抛出这个异常,我们人造了一个空指针异常来验证,如下图所示

异常断点3.png 我们用debug启动后,idea帮我们停在了“s.length()”这里,这里s是空,所以会抛出空指针异常,这样我们就能通过异常断点的方式,直接让idea帮我们定位到了抛出异常的行。

强制返回

想象是否遇到过这样的场景:一个方法debug到底10行,我们已经知道问题了,这时候我们可能不想执行后面的代码了,因为后面的代码可能有写数据库、有远程调用、有发送消息,如果走了后面的代码我们后续要再恢复起来会比较麻烦,这时候我们最常见的操作是直接stop,但是很遗憾,直接stop后,后面的代码还是会被执行到,如下图所示,“这句在for循环之后执行”这句话在debug结束后还是会被打印出来。

强制返回1.png

强制返回2.png 那有没有办法让后续的代码不被执行呢?这里就可以使用强制返回。在Debug时找到Frames模块,里面找到当前正在debug的方法,右键,选择“Force Return”,这样就可以强制返回了,后面的代码不会被执行到。

强制返回3.png 如下图效果,就没有打印出“这句在for循环之后执行”,证明后面的代码缺失没有被执行

强制返回4.png PS:有些同学的idea可能默认没有打开frames模块,可以在右边这个小魔方这里选择打开

打开frames模块.png

抛出异常

有些时候,我们在调用方法的地方加了trycatch,或者用AOP的方式增加了统一的异常捕获,但是某一次在catch模块中处理结果和我们预期不一致,但是我们自己有的测试数据都不会发生异常,那我们就很难debug到catch模块的代码了。这时候我们可以在方法执行的过程中强制抛出某种异常,这样就可以保证debug到异常捕获的代码。 设置的方法跟强制返回类似,选择抛出异常并且在表达式中创建想要抛出的异常即可:

抛出异常1.png

抛出异常2.png 如上图所示,在method1种强制抛出了空指针异常,我们在main方法的catch中就捕捉到了这个异常,这样就可以继续debug捕捉到异常后的处理逻辑,如下图所示

抛出异常3.png

Drop Frame

在debug过程中,有时候在一步步往下走的时候,F8按快了多走了一步,导致关键的一行没有被停止到,这时候我们只能重来一次,如果遇到不太容易触发的分支,重来一次的代价是比较大的,有没有办法回溯呢?当然并没有完整的上一步的功能,但是使用drop frame,可以让某个子方法重新走一遍,一定程度上起到了上一步的作用。例如当前在method1种,走到了第2行,但是第1行是我们的关键行,我们可以drop掉method1这个方法的frame,这样就会回到调用method1的地方,可以再进入一遍。

第一次,过了method1的第1行,当前在第2行,选择method1这里的Drop Frame

drop1.png

这时候回退到调用menthod1的地方

drop2.png

可以按F7重新进入method1内部

drop3.png

执行结果后,可以看到method1的第1行的sout确实被执行了2次

drop4.png

后记

好了,就先整理这些吧,下次新学了debug的姿势,再来这里补充。