UML软件工程组织

 

 

如何在 Rational Sostware Architect 和 Rational Application Developer 中调试程序
 
作者:金发华 陈樟洪, 出处:IBM
 

本文内容包括:

本文首先介绍了 IBM 集成开发环境 Rational Software Architect(RSA),及 Rational Application Developer(RAD)中提供的调试功能,然后介绍了集成开发环境中提供的调试透视图,断点的分类以及调试时程序的执行方法等,列出了程序调试时一些有用的技巧。文章的最后介绍了分析 bug 的一些有用的方法。文中所有的截图和例子都是以 RSA 开发环境作为基础的,RAD 中的界面跟它类似。

RSA 中提供的调试功能

RSA中提供了丰富的调试功能,在对主要的Java Application调试功能(也叫JDT,Java development tools)进行介绍之前,先对RSA中提供的其他调试功能进行简单介绍。

  • J2EE/Web Application调试器,该调试器提供了对运行在Webshpere容器中的web对象调试的支持,如JSP、Servlet,EJB等。当你用这个调试器的时候,你可以通过设定行断点,逐行运行代码和检查变量的值等来控制程序的执行。你也可以单步跟进到标签库代码中。该调试器的使用跟本文下面介绍的关于JDT的调试技巧并无两样。
  • SQL存储过程调试器,该调试器对运行在本地或者远程的DB2存储过程进行调试。你可以在存储过程中设置行和变量断点、运行过程中将DB2存储过程挂住,以及按步执行以及查看变量等。具体的使用请参见在线的帮助。
  • Ant脚本的调试器,用来调试Ant脚本。该调试器也能打行断点、在执行的时候挂住线程,查看当前脚本的运行状况等,在本文后面列出的资源列表中有怎么在Eclipse中使用Ant编辑器的文章,供读者参考。
  • 活动脚本调试器(Active Script debugger),该调试器能够对JavaScript(.js),VBScript文件(.vbs)和包含JS(VBS)的html、jsp页面进行调试。在使用该调试器之前需要先下载微软的Windows Script调试器(可以在这个页面找到http://msdn.microsoft.com/library/default.asp)。具体的使用请参见在线的帮助。

这里只列出了RSA提供的一部分调试器,要知道它提供全部调试器,请参见本文后面的的资源列表。

除了调试器之外,RSA还提供了“Profiling and Logging”,它提供了“代码覆盖率监测”,“代码调用时序图”以及“内存泄漏分析”等很多强大的调试、分析程序功能。本文主要是介绍基本的程序调试,如果想了解更多关于“Profiling and Logging”的信息,请参见RSA的在线帮助。

在这里可以看出,RSA提供了丰富的调试功能,让你在写程序的时候更加灵活和富有效率。这篇文章中主要介绍JDT和J2EE/Web Application调试器。 在 RSA 中调试程序

这一部分首先大概介绍RSA中提供的调试功能和界面,然后介绍断点的种类,最后列出了一些在调试程序的时候有用的最佳实践。在介绍调试功能之前,先介绍一些界面上的概念。

  • 透视图(Perspective)是一个可见的容器(container),它可以包含视图(view)和编辑器(editor)的集合。
  • 视图比较典型地被用来在具有继承关系信息之间的导航,打开一个编辑器或者显示当前活动编辑器的属性等,与editor不同的是,视图里的信息马上被保存。如调试透视图由调试视图,变量视图和控制台视图和代码编辑器等组成。

启动 RSA 调试功能

在介绍调试透视图之前,先看看如何启动RSA的调试功能。

Java应用中启动调试功能:

在代码编辑器打开某个具有main方法的类,在Run菜单中选择“Debug As”,出现的子菜单中选择“Java Application”。如果你是第一次在RSA中启用调试功能的话,RSA会提示你要转入调试透视图,直接单击OK就可以。你也可以选择在工具栏中启动调试功能;或者Package Explorer视图中选择某个具有main方法的类,右击选择“Debug”->“Java Application”。

Web应用中启动调试功能:

要使Web应用具有调试功能,必须让Server以调试方式启动。如下图所示,选中目标服务器->右击->选择“Debug”,则服务器将以调试方式启动。
图 1:Web 应用中启动调试功能
Web 应用中启动调试功能

RSA 中的调试透视图(Debug Perspective)

图 2:RSA 中的调试透视图
RSA 中的调试透视图

如上图所示,RSA中的调试透视图可以分成三部分(提示:如果您的透视图跟本文所列的不一致或者默认的透视图被打乱了,可以通过Windows->Reset Perspective来恢复默认状态)。

上面的一排视图:

左上角是“调试视图(Debug View)”,在该视图中显示了正在当前运行的线程。如果当前在同时调试两个程序的话,则会有两个线程出现。比如现在需要调试一个Server Socket的应用,一般来说调试这种程序还需要客户端的应用去连接Server端的Socket应用。这种情况下,在该视图中就会有两个线程出线,一个是服务器端的Server Socket线程,另外的就是客户端应用的线程。在线程视图中可以看见程序的调用堆栈,如上图所示,能看出调用的顺序是:在调用test之前的是main方法。

右上角在默认的情况只列出了两种视图,变量(Variable)和断点(Breakpoints)视图。

在变量视图中,会显示在当前的线程中,所有可见变量的值,该视图在调试的时候经常被用到。断点视图中显示的是在当前的工作区(workspace)中所有的断点,也可以在该视图中选择一个断点,对该断点的属性进行编辑。另外,表达式(Expressions)视图也会在该区域显示(点击Window-> Show view->Expressions可以看到该视图),通过该视图可以自己定义表达式,并查看该表达式的值,这在查看某些特殊的变量值的时候特别有用。如下图所示的就是表达式视图,在这里运行到某代码行的时候,查看表达式某Vector变量里的内容。

图 3:变量(Variable)和断点(Breakpoints)视图
变量(Variable)和断点(Breakpoints)视图
 

中间的一排视图:

主要是由代码编辑器和Outline视图,用来查看代码和显示方法列表。

下面的一排视图:

主要是控制台(Console)视图,用来查看打印出的消息。另外,显示(Display)视图也会在该区域显示(点击Window-> Show view->Display可以看到该视图),该视图中可以写多个Java表达式,实现对一些特殊的变量重新赋值,在稍后的调试技巧中会详细讨论到。下图所示的就是显示视图。

图 4:控制台(Console)视图
控制台(Console)视图

RSA 中断点的种类和调试技巧

RSA中可以分成四种断点:行断点,计数断点,变量值改变断点(value of condition change)和条件断点。各种断点都可以被暂时禁用(disable)或者启用(enable),如果你想在某些时候不需要一些断点但是又不想去掉这些断点,可以禁用断点(右击断点,选择“禁用断点”),在需要的时候再打开这些断点。当然,断点也能被去掉(右击某断点,选择“去掉断点”)。下面对各种断点做详细介绍。

行断点:一般在怀疑程序有问题的地方打这种断点,设置这种断点很简单,只需在代码行前双击即可,或者右击代码行前,选择“添加断点”。行断点可以认为是没有设置任何属性的断点。任何时候进入该断点,程序都会被挂起。下图所示,如何打行断点。


图 5:行断点
行断点

计数断点:此类型断点只在进入该断点n次后才把线程挂住。一般的行断点设置之后,每次进入该断点都会挂住线程等待用户执行,但是有时候并不需要这样,而是在满足给定的次数之后才将线程挂住。比如有下面所示的代码,


图 6:计数断点
计数断点

假设在直接运行(不是调试)上面这段代码的时候,发现在取得“;”的index这行代码报错,然后你可能在这行代码前打一断点,并进入调试程序的状态。在调试程序的时候发现代码行报错不是在每个循环的时候都会出现,而是在第8次进入循环的时候发现该行报的错(比如NullPointerException)。因此如果有某种原因你想再次调试这段代码的时候,如果没有计数断点的话,则需要7次进入该断点将线程挂住,这样很麻烦。计数断点在这个时候就非常有用。

计数断点的设置,右击行断点,选择“断点属性”(也可以在断点视图中,选择某断点,选择“断点属性”),在出现的行断点属性设置窗口中,选中“启用命中计数”选择框,并且在后面的输入框中输入计数次数(此处是8次)。


图 7:计数断点的设置
计数断点的设置
 

变量值改变断点:这种类型的断点会在某变量的值变化后将线程挂住等待用户的执行,这种断点在跟踪某变量值变化的时候非常有用。比如在调试程序的时候会有这种情况:某个静态变量的值在你的期望中是不会改变的,但是事实上程序执行的时候该变量的值却被改了,这可能是代码的误写引起的,但是你可能又不知道什么时候引起了这个变量值的改变,这个时候这种断点就派上用场了。进入该变量值改变断点后,在调试视图中就可以看到方法的调用堆栈,通过这个堆栈可以分析出在什么时候改变了变量的值。作为举例,请看如下图所示代码:假设我在System.out.println(temp); 前打一行断点,但是我只想在temp的值改变的时候将线程挂住。


图 8:变量值改变断点
变量值改变断点
 

变量值改变断点的设置,右击这个行断点,点击“属性”,如下图所示:选中“启用条件(Enable Condition)”,并在输入框中输入观察的变量值,这里是temp;并在“暂挂(Suspend when)”中选择“条件的值更改”。这时候,只有在temp的值改变的时候会将线程会挂住(只有3次,即在temp=0,temp=2,temp=4),而不是5次。


图 9:变量值改变断点的设置
变量值改变断点的设置
 

条件断点:这种断点只有在符合某条件表达式的时候才会进入断点并且挂起线程。作为举例,比如有下面的一段代码。在该方法的最后返回语句之前打一断点,但是我并不想在每次方法返回之前都将线程挂住,而只想在返回值resulst为1的时候将线程挂住。


图 10:条件断点
条件断点
 

条件断点的设置,右击这个行断点,点击“属性”,如下图所示:选中“启用条件(Enable Condition)”,并在下面的输入框中输入表达式result == 1;并在“暂挂(Suspend when)”中选择“条件为true(condition is “true”)”。这样只有在传入的参数中包含有“;”或者“.”的时候,才会进入该断点并且挂住。


图 11:条件断点的设置
条件断点的设置
 

程序调试时执行方法

在RSA中在调试时程序的执行方法主要有6种:单步跳入(Step into),单步跳过(Step over),单步返回(Step return),继续(Resume),执行到当前行(Run to line)和终止(Terminate),下面分别做介绍。

  • 单步跳入(快捷键是F5):进入下一个即将被执行到的代码行。一般来说,如果单步跳入时候已经到第三方提供的jar或者系统的jar中的时候,应该单步返回,或者直接“继续”到下一个断点,除非你有特别的需要,要调试jar里的代码。
  • 单步跳过(快捷键是F6):执行当前被选中的行,并且在下个即将被执行的代码行中挂住线程。一般来说,这种方式在监视某个代码行的执行情况,但是并不关心这个代码行的调用方式。比如可能是调用一个方法,但是你只关心所调用方法返回的结果。
  • 单步返回(快捷键是F7):回到执行堆栈中的上一个return语句中。一般来说,可能会用在不小心用单步跳入的时候,跳出当前的执行代码,或者不关心当前方法的执行过程。
  • 继续(快捷键是F8):继续执行到下一个断点。
  • 执行到当前行(快捷键是Ctrl + R):将光标放在某行,程序执行到光标所在行并且挂住线程。这个可以作为打临时断点的一种方法。
  • 终止:停止当前运行的线程。

调试技巧

查看变量的值

在“表达式(Expressions)”视图中右击,选择“Add Watch Expression”:


图 12:“Add Watch Expression”
“Add Watch Expression”
 

在出现的窗口中加入如下的表达式,单击OK。


图 13:加入的表达式
加入的表达式
 

在表单式视图中就看见了求值后的值,如果这个值的内容比较多,在左边的窗口中只会选择一部分,在右边的窗口中会把所有的值都显示出来。


图 14:求值后的值
求值后的值
 

对于已经写好的表达式,可以重新改写表达式(Edit Watch Expression)或者重新求值(Reevaluate Watch Expression)等操作,当然也可以将表达式都删除(Remove)或者无效(Disable)如下图所示:


图 15:重新改写表达式
重新改写表达式
 

调试期间更改变量的值

有时候可能会需要在调试的时候更改变量的值。比如在调试过程中出现这种情况:测试组发现了一个bug,后来你发现是由于从数据库中返回的某个字段的值引起的(比如说是一个很简单的int型值,是一个标志位)。你修改完代码后需要重新测试程序,但是非常不幸的是你本地的数据库中没有存在这样的一条记录:这条记录的标志位字段的值跟产生bug的记录的值是一样的。而且可能对于你来说,不能随意修改数据库中的值。这时候,修改变量的值就有用了。另外,修改变量在这种情况下也非常有用:在调试程序的时候,发现一个地方的代码已经出问题了,导致传给接下来调用的方法的参数不对,但是你又非常想看接下来的方法调用是否正确。正常情况下可能需要先停止当前调试线程,修改完代码后重新调试。这时候一个比较好的办法就是在进入被调用方法的入口出改成正确的参数,先看一下接下来的方法调用是否正确,这样能节省调试程序的时间,特别是在有的应用程序重新启动花费比较长时间的时候。

修改变量可以有两种方法:在变量视图中修改和在显示视图中修改。

修改变量可以有两种方法:在变量视图中修改和在显示视图中修改。变量视图中修改:在变量视图中选中待要修改的变量,右击,选中“改变值(Change value)”;或者直接双击待要修改的变量。


图 16:变量视图中修改
变量视图中修改
 

在弹出的窗口中输入更新后的值,确定后单击“OK”。


图 17:输入更新后的值
输入更新后的值
 

改变后的变量值在变量视图中红色显示。


图 18:改变后的变量值
改变后的变量值
 

在变量视图中修改有一定的局限性,比如对某String类型的变量想要设置成null是不行的,因为不能在变量视图中直接双击那个String变量,然后输入值null,如果那么做的话只是把String变量的值变成了”null”;另外如果想往某些List变量里加入、删除一些值的话,在这个视图里也是不行的,比如在某Vector变量中,需要临时加入一个Student对象,在变量视图中也是办不到的。对于象上面提到的这些变量的更改,可以在显示(Display)视图中进行。在下面这段代码中,假设外面传入的值是“This is a test string.”,现在我想把参数para设置成null。
图 19:在显示(Display)视图中进行修改

打开显示视图,输入语句。

图 20:打开显示视图,输入语句。
打开显示视图,输入语句。
 

选中写好的语句,右击执行。


图 21:选中写好的语句,右击执行。
选中写好的语句,右击执行。

回到变量视图后发现已经变成null了,而不是”null”。


图 22:回到变量视图
回到变量视图
 

如果想执行多个语句,则跟此类似,在显示视图中输入多个语句,选中多条语句后执行。当然,这里执行的语句在跟线程执行的上下文有关系的情况下才有意义,比如你可以在显示视图中随便输入没有语法错误的初始化一个Vector变量的语句,并往里加入对象,选中后确实能执行,但是执行完之后没有意义,只有当你在这里输入的变量在当前的线程环境下能被识别的才有意义。就刚才的Vector变量的例子来说,如果这个变量在当前的线程环境下能找到,则重新初始化然后加入对象这样的操作才起作用。

附加源码

Java社区里提供的开源代码丰富多彩,但是开源社区中提供的代码质量也是参差不齐,因此有时候你可能需要跟踪到第三方提供的包中进行调试,判断是否是第三方提供的包有问题。在一般的IDE环境中都提供了对Java第三方代码调试的功能,RSA也不另外。

右击工程->属性->左边选择“Java编译路径(Java Build Path)” ->右边选择“库(Libraries)”,在出现的jar中,选择你要添加源码的jar文件,单击前面的“+”号,如下图所示:


图 23:添加源码的jar文件
添加源码的jar文件
 

选择“附源码(Source attachment)”,单击右侧的“编辑”按钮,在出现的窗口中选择源代码所在的位置,如下图所示:


图 24:选择源代码所在的位置
选择源代码所在的位置
 

为方便管理你可以把所有的源代码打成一个压缩包拷贝到工程的某个目录下;第三方提供的源代码也可以放在外部的目录中(不在工作区中)。根据你自己的情况,选择相应的按钮加入源代码,如下图所示:


图 25:把所有的源代码打成一个压缩包拷贝到工程的某个目录下
把所有的源代码打成一个压缩包拷贝到工程的某个目录下
 

这里的例子是Apache提供的httpclient,现在程序调试的时候可以进入httpclient提供的源代码中了,下图所示:


图 26:进入httpclient提供的源代码中
进入httpclient提供的源代码中
 

步过滤器(Step Filter)

另外,RSA还提供了“步过滤器(Step Filter)”的功能:当程序跟进到你不想进行调试的代码段,在这种情况下你可以设置Step Filter,这样在调试时,在满足Filter指定的条件后将不进入。Filter的设置可以通过“Windows->Preference->Java->Debug->Step Filtering”,在出现的窗口中加入你要过滤的类和包等,如下图所示:


图 27:“步过滤器(Step Filter)”的功能
“步过滤器(Step Filter)”的功能
 

要让过滤器在调试的时候起作用,必须在调试透视图中选上“Use Step Filters”,“Run->Use Step Filters”(或者快捷键Shift + F5),则过滤器将会起作用。 分析调试 Bug

在介绍如何分析调试bug之前,先说一下bug从发现者的角度进行的分类。

开发人员发现的 Bug

这种bug是在写单元测试或者自己测试程序的时候发现的:产生bug的环境就是开发人员的环境,而且程序是自己开发的,对代码比较清楚,修复起来比较容易。

在测试环境或者产品环境,由测试人员或者客户发现的bug

  • 产生bug的环境和开发环境不是同一个(可能部署的应用程序服务器不同、数据库中的数据也不一样),并且有时候模拟测试或者生产环境比较困难。
  • 由于不能用调试工具,只能依赖于程序的日志输出来判断程序的运行状况。如果没有好的日志输出将很难定位程序产生bug的地方。

分析 Bug

下面介绍如何分析在测试环境或者产品环境下产生的bug。

  • 在拿到bug修复的任务后,第一个需要做的就是“重现bug”。重现bug其实有两个阶段:第一步,如果bug由测试人员在测试环境里发现的话,那么你有可能可以在测试环境中按照测试员发现bug的过程来重现它,但是在产品环境中,由于不能随便更改数据,你可能只能通过用户的描述,然后转到下面写的第二步;第二步,尽可能在自己的开发环境中重现bug,但是这个步骤有时候未必能够在你自己的环境中实现,或者实现很困难,这时候就需要通过开发工具提供的一些功能来帮助你重现bug。有的bug可能是经过好几个步骤才会出现,因此你需要仔细测试程序并思考代码,确定大概可能会出错的地方。
  • 仔细检查程序的日志输出,将出错位置定位到某方法。说到日志,Apache的log4j是一个不错的选择(在本地开发时,将日志输出到控制台;测试或者产品环境,将日志输出到日志文件中)。一般来说,可以在每个方法的入口处加入:entering method foo(), 方法的出口处加入:existing method foo()。如果觉得每个方法都需要加入这两个语句很麻烦的话,可以考虑使用一些框架(如AOP-Aspect Orient Programming,利用它的代码注入技术在方法的入口和出口加入日志)来帮助你解决一些问题。
  • 定位到某方法之后,检查到底发生了什么错误。可以通过检查日志或者Java的异常堆栈得到错误信息。
  • 找出发生错误的代码行,如果日志信息足够详细――能够打印出当前的变量值的话,对分析Bug是相当有用的。
  • 如果能看到在程序出错之前输出的变量值,仔细想这些值。是错的还是对的?如果是错的,那应该是什么值?为什么会出错?如果不能知道变量的值,只能通过分析日志中的异常堆栈信息来找出bug的原因。
  • 定位了错误的代码后,尽可能在本地的开发环境重现,然后修改代码在本地测试通过。

程序调试的难题

不管是修复哪种bug,最终都需要在开发环境中重新修改代码然后再做测试(本地开发人员做的测试)。对于测试那些在非本地开发环境中发现的bug,可能需要模拟发现bug的运行环境,但是有时候模拟这个环境非常困难或者几乎不可能,比如说有的bug的出现跟数据库中的数据有关,但是在自己本地的开发环境中难以模拟或者不方便,在这种情况下,可以通过上文介绍的调试技巧来解决你的问题。

结论

本文介绍了RSA提供的调试功能,以及一些有用的调试技巧,让你更好地解决在程序调试中出现的一些问题,文章的最后介绍了分析bug时候一些有用的方法。

参考资料

 

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号