最好的IDEA debug长文?看完我佛了(上)

简介: 最好的IDEA debug长文?看完我佛了(上)

本文提纲


image.pngimage.png


版本约定


  • IntelliJ IDEA:2020.3.2

小插曲:IDEA刚发布了其2020.3.2这个小版本,启动图换成了20周年图,IntelliJ IDEA 20周岁啦,为期2天的周年庆活动对开发者免费开放,感受一下:


image.png


正文


Debug调试对IT从业者不是个陌生概念,工作中经常会用到它,这无关乎于初级、中级、高级程序员。调试程序的方式有多种,如:输出日志、增加辅助变量、拆分函数体、断点调试等等,本文将介绍的是断点调试 – 一种最行之有效的调试方法。准确的讲,本文讲述是使用IntelliJ IDEA断点调试。


Debug用来追踪代码的运行流程,通常在程序运行过程中出现异常时,启用Debug模式可以分析定位异常发生的位置,以及在运行过程中参数的变化。除此之外,我们也可以使用Debug模式来跟踪代码的运行流程来学习优秀的开源框架。


断点调试有多重要?


俗话说编码5分钟,debug2小时,从这句话就能体现出调试的重要性,毕竟它占据你“大部分”的时间。


为了真实的体现出它的重要性,我“引经据典”,找来了几个资深行业经验的大佬用引用他们的话来表述:


  1. 调试技巧比编码技巧更为重要,因为花费在调试上的时间往往比编码还多,学到的东西比编码中学到的更丰富
  2. 调试技能重要性甚⾄超过学习⼀门语⾔
  3. 不会调试的程序员,肯定编制不出任何好的软件


我把关键词都加粗划重点了,其重要性可见一斑。大佬尚且这么认为,何况是我等?所以,本文好好阅读O(∩_∩)O哈哈~


什么是断点?

突然被这么一问,是不是脑袋懵懵的?


一个天天都在用的“东西”,若是真要你对它下个定义说给别人听,估计一时半会还解释不清。当然喽,大道至简,领会其要义能熟练使用才是硬道理。本文作为一篇“严肃”的技术文章,自然需要先把断点这个概念用文字描述出来。


断点:为了调试而故意让程序暂停的地方。它是一种附加在源代码上面的特殊标记,在debug模式下可以触发特定动作,如暂停执行、打印线程堆栈、计算表达式的值、变量跟踪等等。断点的设置和取消全人为手动管理,若不手动处理(删除)将会和项目一直存在。


如果你看过前两篇文章,一定能解释为何它会一直存在项目里。建议你前往参阅,电梯直达


可见,断点的核心要义是暂停程序,从而在暂停那时刻就可以看到上线文的变量情况、IO情况、线程情况等信息,进而更深入的了解程序,及时发现问题和追踪到错误的根源。


断点参数


断点并不是孤立存在的,它也可以有参数,从而定制出不同的断点行为,让其能在不同条件下生效,这个参数就叫断点参数。


我们平时用得比较多的条件断点,它就是断点参数的最典型应用。当然除了条件断点,其它的断点类型也是可以定制化参数的。那到底有哪些断点类型可以使用和定制呢?那么接下来就步入到本文主体内容,开始进入更有意思的部分啦。


断点的基本使用


应该没人不会打断点吧,即使你是产品经理(产品经理莫名躺枪,手动狗头~)。


打断点最简单最直接的方式就是在你想设置断点的哪一行代码的最左边窗栏鼠标左键单击一下,完成后能看到一个小红点,就表示断点设置成功啦,再点击一下就取消。形如这样:


20210128002636663.gif


因为我的IDEA界面简洁,尽可能的去掉了“按钮”,所以平时我自己是使用到大量的快捷键来操作IDEA,打断点也是如此经常用快捷键去完成。当然喽,很多时候也用鼠标的啦,毕竟鼠标处理还是有其很大优势的。


说明:我的快捷键是Ctrl + Shift + B,仅供给你参考


管理断点


管理断点包括新增、删除断点。


对于少量断点来讲,鼠标一个个的点击给它删除掉是可以的。但若打了“大量”的断点在代码里(比如看xxx源码的时候),这时让去一个个找来删除是不太现实的,毕竟你可能自己都忘了哪儿有断点。这个时候一个管理页面/窗口就显得格外的重要了,在IDEA中提供了这样的窗口,你有多种方式打开它:


  1. 菜单栏方式:Run -> view breakpoints,缺点是路径太长太麻烦
  2. Actions方式:双击shift调出Actions窗口,输入view breakpoints即可打开
  3. 任意断点处鼠标右键:选择more即可打开管理窗口。缺点是:你至少得找到一个断点作为抓手(当然喽你可以任意处随意打一个点进去也成)
  4. 调试窗口:该打开方式下面会提到
  5. 快捷键方式:毫无疑问,这是我最为推荐的方式喽



在这个管理页面,你可以对断点进行增删改。


说明:我的快捷键是Ctrl + Shift + F8,仅供给你参考


如何debug模式运行?


额,这讲得是不是有点过于简单了点。


启动Debug模式运行的方式有多种,比如工具栏的虫子小图标按钮、程序方法入口左键点击、菜单栏、右键菜单栏等等,下面简单演示下 :


image.gif'


据我了解,很多同学最常用的方式是点击上方工具栏右上角的虫子图标,因为我“没有”这个图标,所以“教程”中就不演示了。A哥平时99%情况下都是使用快捷键方式启动程序,因为我认为那是最迅速和便捷的(当然不一定适合你)。


此功能我的快捷键分为两大类


1/运行右上角当前选中的入口类。它有一组快捷键

  1. Ctrl + Shift + Alt + enter:Run运行
  2. Ctrl + Shift + Alt + \:Debug运行


2.因为很多时候需要从新的入口启动程序,做Spring Boot工程开发可能体会不到(入口只有一个),但在做教程、Demo的时候程序入口是经常变化的,所以不可能每次都还人肉去改启动类,效率太低。为此我就新设置了这组快捷键

  1. Ctrl + Shift + Alt + [:Run运行,鼠标焦点所在作为入口
  2. Ctrl + Shift + Alt + ]:Debug运行,鼠标焦点所在入口


另外,若要区分本次是Run运行还是Debug运行,除了看右上角小虫子图标外,更好的方式看底部控制台窗口激活的是哪个。这样看的优点是:即使同一份应用启动多次,也能快速看出来哪些debug哪些run。


image.png


-image.png


值得一提的是:debug模式运行,若没有任何断点被激活(比如你压根就没打断点),效果和run模式启动是一样(但控制台窗口不一样,因此日志输出的位置也就不一样)。


调试窗口详解


调试窗口是我们断点调试的操作面板,熟练的使用此面板推提高效率和掌握更多技巧非常重要。先来认识下它:


image.png


此操作面板上按钮不少,对Debug调试有多熟练很大程度上是由操作此面板的熟练度决定的。


调试按钮


最常用的一排按钮,入门必备。


image.png

一共9个按钮,从左往右依次解释下:


1.image.png

Show Execution Point:回到当前激活的断点处。效果:若你鼠标现在在别的页面/别的类上面,点击它快速“归位”


2.image.png

Step Over步过:也叫单步调试,一行一行往下走,若这一行是方法也不会进入里面去。这个应该是平时使用得最多的按钮了,没有之一。所以,建议记住你的快捷键来提高效率哈


3.image.pngStep Into步入:进入方法体内部。这里的方法指的你自定义的方法or三方库的方法,不会进入到JDK官方的方法里(如上面的System.out.println()这种它是不会进去的)


4.image.png

Force Step Into强制步入:能进入任何方法,包括JDK的。一般查看底层源码才会用到它


5.image.pngStep Out步出:它是搭配(Force) Step Into一起使用的,当通过step into进入到方法体内部想出来时,一般有两种方案:单步调试慢慢出来,另一个就是step out(推荐)


6.image.pngDrop frame:回到当前方法的调用处,同时上下文内所有的变量的值也回到那个时候。该按钮能够点击的前提条件是:当前所处的方法有上级方法,如果你是main方法里,那么按钮就是灰色喽


7.image.pngRun to Cursor运行到光标处:你想要代码在哪里停一下,就把光标放在哪就成。这个功能实在太好用了,大大缓解了密密麻麻的断点,强烈推荐


8.image.pngEvaluate Expression表达式计算器:看图标就是个计算器嘛,所以你可以在这里执行任何合法的表达式


image.png


9.image.pngTrace Current Stream Chain跟踪当前Stream流:只有代码停在Stream流语句上,此图标才点亮可以被点击。这是IDEA提供的由于调试Stream流的杀手锏级别的功能,放在文下详细解释


这一排按钮非常重要,甚至是最重要,一定要熟练掌握,可以大大提高调试代码的效率,亲测有效。


服务按钮


把最左边一竖排定义为服务按钮,为调试过程提供服务。


image.png


一共10个,但都比较简单和好理解。同样的从上到下过一遍:


  1. Rerun xxx:关闭当前程序,重新运行


  1. Modify Run Configuration:顾名思义,修改运行的配置。点击此按钮的效果同点击右上角的框框:

image.png


点击会弹出这个配置窗口:


image.png

每份运行期配置都是具名且唯一的,互相隔离。运行配置可修改的项非常多,大概如下:


说明:我截图的页面可能和你不一样,因为我用的是最新版的IDEA,此页面在2020.3版本做了改版


image.png


4. Resume Program:恢复程序。当断点激活时程序“停止”了,点击这个按钮就是恢复的意思。它给到的效果是:跳到下一个断点(用这句话解释貌似更容易理解些),若后面没有断点就直接运行结束了。这个按钮非常常用。


5. Pause Program:暂停程序。嗯,只要你现在“卡”在断点处,那么状态就是Pause的状态。这时候就有疑问了,难道这个按钮一直是灰色不可点状态?有啥用呢?我网络上看了看,几乎没人能够解释它的作用,这里A哥尝试给你解释下,用张图给你整得明明白白,服服帖帖:


20210128112350259.gif


6. Stop xxx:不解释

7. View Breakpoints:打开断点管理窗口。文上已详细解释了此窗口的用法

8. Mute Breakpoints:这个按钮挺有意思的,作用是让所有断点变为灰色,也就是说让它们失效。它是一个批量操作,操作对象是所有断点,而不可针对于某一个。若你现在不想把所有断点删除,但又不想它们阻拦你,那么可用这个按钮实现

9. Get Thread Dump:拿到当前线程的dump,可以查看到当前线程的状态。如下图:


image.png


image.png


10. Settings:打开设置菜单。属于高级使用,每一项开启后有什么效果,放在文下解释


image.png


11. Pin tab:如果你这会调试xxx这个程序很频繁,那么把它“钉”上会更有助于效率提升


方法调用栈


显示当前方法(位于栈顶)所经过的所有方法。


20210128133715639.gif


说明:点击右上角的小漏斗图标可以不显示类库的方法,只显示你自己写的方法,方便调试


变量区Variables


在此区域可以查看当前断点上下文范围内的所有变量值(即使不在本类内也可以点过去查看哦),包括static静态的。

20210128134700488.gif


值得注意:此区域里的变量IDEA会自动调用其toString()方法,因此若你遇到正常运行只输出一句日志,debug输出多句这种case很可能就是这个情况哦。


Watches变量跟踪


有的时候变量很多,而只需要重点关注某几个变量,就可以使用Watches。


20210128135410244.gif


除了以上这些,还有什么动态改变变量值set Value,跳转到源码处jump to source等都是非常实用的功能,这就留你自己开发和实验哈。


为何调试窗口没自动打开?


有同学遇到过这个情况:明明断点激活了(程序暂停了),但是那个“操作面板”并没有出来,怎么破?


话不多说,检查你的这个配置项是勾选状态即可。这个状态IDEA默认是勾选上的,一般不用操心。


image.png


断点调试的奇淫巧技


最后,站在使用层面,介绍些非常实用的“奇淫巧技”给你,这些小技巧可拿来就用。


强制返回(中断debug)

场景描述:调试时,当我走到第三步就发现了问题,这个时候并不希望走完后续流程(比如因为前面有bug后续流程会有删除数据操作等等),这个时候怎么处理?


咔嚓,Stop程序。是的,很长一段时间里我也是这么干的,确实能达到目的。直到我发现了一个更优雅的方法:Force Return,效果为:强制返回方法返回值(自己给个值)来避免后续的流程。


20210129161039426.gif


条件断点


指定断点的激活条件,都能称作条件断点。一般情况下,在行断点下给定一个计算表达式,结果为true就激活断点这是最常用的方式。因为上面已有案例,这里省略


多线程调试


多线程程序的好处固然不用多说,但总所周知它调试起来是比较困难的,比如这段:


public static void main(String[] args) {
    // 共放3个"令牌"
    CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
    // 模拟多个线程去抢
    for (int i = 0; i < 10; i++) {
        new Thread(() -> {
            try {
                String name = Thread.currentThread().getName();
                System.out.println(name + ",准备抢令牌");
                cyclicBarrier.await();
                System.out.println(name + ",已抢到");
            } catch (Exception e) {
            }
        }, "线程" + i).start();
    }
}


这个时候如果你想研究await()方法的实现,需要具备的前提条件是多个线程进入,因此需要hold住多个线程。若只是在await()这一行打个普通的行断点,那结果是这样子的:


image.png


所有线程都是Running状态,显示这是不可能的,因为总共只有3个另外,拿完了其它的都得等待才对,所以这个根本就不是真实的执行场景,也就不可能跟踪到await()方法里面去探究其实现。


为了模拟出这种场景进行调试,就对断点阻塞条件设置为这样:


image.png


再次运行程序,线程情况如下:


image.png

相关文章
|
2月前
|
Java 内存技术
IDEA工具debug的小技巧
IDEA工具debug的小技巧
33 0
|
2月前
|
Java
IDEA debug HashMap源码的心得
IDEA debug HashMap源码的心得
28 0
|
5天前
|
Java
idea远程debug应用
idea 中debug现场部署的Java应用
5 1
|
1月前
|
Java Spring
idea Spring-boot 项目debug启动过慢 spring debug启动过慢解决办法:已解决
idea Spring-boot 项目debug启动过慢 spring debug启动过慢解决办法:已解决
37 3
|
9天前
idea远程调试debug
配置idea远程debug
7 0
|
25天前
|
监控 前端开发
IDEA Debug技巧大全,看完就能提升工作效率
IDEA Debug技巧大全,看完就能提升工作效率
18 0
|
2月前
|
Java
IDEA DeBug
IDEA DeBug
22 1
|
2月前
|
Java 测试技术
使用IDEA进行服务器远程debug调试
使用IDEA进行服务器远程debug调试
52 0
|
2月前
IDEA远程调试Remote Debug
IDEA远程调试Remote Debug
32 0
|
2月前
|
监控 应用服务中间件
idea debug模式启动Tomcat报错:Error running ‘tomcat8‘: java.net.SocketException “socket closed“
idea debug模式启动Tomcat报错:Error running ‘tomcat8‘: java.net.SocketException “socket closed“