调试的技巧:加代码与做对比

摘自 http://blog.csdn.net/BinaryTreeEx?viewmode=contents 名为“十八年开发经验分享(二)问题解决篇(上)”的文章。

1,调试循环的技巧

有时候我们可能需要写一点代码(这些代码还不能算是测试代码),为使用断点设置这个调试手段起到辅助的作用。比如,错误的位置是在一个循环体内。如果我们直接简单的在循环体内设置断点,那么循环执行的第一次就停在断点处,然后开始单步跟踪。这时就会有一个问题,这个循环要跟踪多少次,才能等到出现错误的那次循环呢?如果是第一次,或者前几次那还好,如果是100次,200次或者更多次那就麻烦了。对于这样的场合就需要写点代码来辅助调试了。假设循环是for循环,那么在循环体内的恰当位置写下类似下面的代码:


for (int i = 0; ...; i++)
{
      // 这些是需要调试的代码,已经存在的,假设有若干行
      ... 
      // 这个if语句是需要加入的调试代码
      if (i == 100)
      {
           int a = 10;      // 断点设置在这行代码上
      }
      // 这些是需要调试的代码,已经存在的,假设有若干行
      ... 
}

调试代码起到的效果是,在循环到第101次时程序就会停在断点处了。这可以显著提高调试的效率。


2,调试消息处理的技巧

还有一些场合我们调试的程序和Windows的消息有关,这个时候断点的设置位置和时机就会比较麻烦。比如,当需要把断点设置在鼠标事件中时或者OnPaint事件时,就会这样。因为直接设置的话,那么每次鼠标事件或者OnPaint事件触发时都会导致程序停下来。而这时还远远没到错误出现的时候。这时可以考虑先不设置断点,等到最后一步操作前再设置断点。比如,需要将用户输入的数据在OnPaint事件中显示在客户区。那么可以考虑在恰当的位置执行一行刷新客户区的代码(对于C#的窗体是Invalidate,对于MFC还可以考虑发消息),断点先设置在那行刷新的代码上。等程序执行停在那行代码时,再在OnPaint方法中设置断点,然后按F5,这样就可以让OnPaint中断点直接停在我们需要的时刻上。再给出一个可供选择的方案是,如果可能,将OnPaint中的代码拿出来,放到按钮的点击事件中,这样调试就可以避开原来的麻烦了。更一般的思路是,在程序中加入调试代码,一般是一个if语句。该语句的条件表示了你希望程序停下来单步跟踪的时刻,然后将断点设置在这个if语句内部的代码上就可以了。


3.有些场合可以考虑使用控制台输出信息的办法。当然也可以选择写文件,作用是一样的,但是对于Visual Studio开发环境来说控制台输出对调试程序更为方便些。(这种方法即是输出MessageBox一样的思想,不过输出的数据更多罢了)

在程序的特定位置写入一些输出信息到控制台的代码,C#中我用Console类的WriteLine方法,或者Debug类的同名方法。这样程序的执行不会被打断,同时又能看到必要的信息。这是一个很不错的优点。在调试的时候,如果觉得有困难,错误位置无法确定,那么我建议可以采取逐步解决的办法。先实现最简单的情况,然后调试通过,接着再实现下一个情况,然后再调试通过。如果可能这里我想强调一下,这里分步依次实现的情况最好能够做到独立。比如,代码可以用明显的if else语句或者switch语句的分支隔开,或者代码放在不同的方法中。这样在调试程序时可以让我们每次只关注在一个情况上,并且处理不同情况的代码至少在视觉上没有相互干扰,这对我们解决问题是有帮助的。这个方法在很多场合是很有效的,用好的话还是简化问题的方法。在开发EntityModelStudio的时序图时我采用的就是这个方法,所不同的是时序图的各种操作行为的分类计数对新手来说是一个有点难度的问题。


4.两个有用的技巧
再介绍两个个人认为很有用的技巧,那就是对比法和关键点查找,这是我在工作第一年维修家电时积累的经验,实践发现在软件开发中也是有用的。所谓对比法就是手上有一份代码(或者例子)可以实现要求功能,而我们现在需要实现相同的功能,那么我们就可以对照着例子改,直至实现需要的功能。听上去很简单,事实上这个方法的表述也确实很简单,也许会有人觉得这和google或者百度后的copy/paste有区别么?应该说都会用到copy/paste,这点相同,但是思路上不同,这是区别。我用前几天在CSDN上看到的一个帖子作为例子来说明。

帖子中的有如下的代码(不是原文,但是意思相同):
public class MyClass
{
     int Age {get; set;}
}

帖子的问题是:给MyClass对象的Age属性赋值,提示出错。这当然是一个很新手的初级问题,直接加上public修饰符就可以解决问题。下面试一下用对比法来解决。

问题的现象是不能通过对象访问属性,而事实是应该可以访问,那么就要试图去找一个例子,而那个例子是可以通过对象访问属性的。这样的例子上网很容易找,然后对比差异,应该很快发现差一个public,加上就可以了。这个和copy/paste
还是接近的。但是有的时候找不到这个例子怎么办?那么我们可以通过对比别的内容来尝试解决问题。比如,现在问题是通过对象访问不了属性,那么可以通过对象访问方法么?如果找到通过对象可以访问方法的例子,那么就可以考虑通过调用方法的例子来修改属性。这个就是思维技巧的差异了,要灵活应用方法。在加大一点难度,那么上不了网怎么办?这时可以考虑通过现有工程中已有的代码作为例子。这个问题的难度应该说不大,但是思考的步骤对于新手来说应该是有点挑战的。从解决问题的思考技巧来说,如果一个新手自己就可以有这样的思路,那我就认为这个新手是有才的。希望新手可以体会一下。不同的技巧使用对比法可以在更为复杂的情况下解决难得多的问题,这部分在下篇讨论了。

从我个人的经验来说,新手在学习开发或者一个开发者开始一个新的开发方向的时候,典型的就是使用一门新的语言,会遇到一些无法用常理可以解释的问题。比如我自己的第一个Windows程序。我用了很短时间将30行左右的代码敲入计算机,但是却用了几乎整整八天的时间才调试通过。这个Hello World级别的程序,最终查出的原因是工程名不对。我用的是abc,改成aaa就可以了。这类问题在此后的开发经历中也遇到过,但是总体是越来越少,解决问题所耗费的时间越来越短。但是其共同的特征是无法用常理解释或者莫名其妙的自己消失了。遇到这类问题时,首先确保当前的工程足够简单。如果没有足够简单的工程,可以考虑新建一个。然后可以尝试逐步注释代码找出问题原因。或者先构造一个足够简单并可以通过调试的程序,然后一步一步的修改朝目标逼近。在这一过程中哪一步修改出了问题,那么问题就在那一步上。当然最好每逼近一步就做一次备份。总体上来说这个方法也是属于对比法的范畴。

所谓关键点查找是指程序执行在时间上是顺序的,由此总体上实际的代码也是顺序执行的。那么一旦程序出现问题,我们就可以把程序在出问题的点上分成两部分,出问题之前的和出问题之后的。如果我们看到的结果是正确的,那么问题点应该在当前时间点后执行的代码中,注意我说的是时间点的先后,不是源代码物理位置上的先后。所以我们应该在那些代码中去查找,并在恰当的位置设置断点。前面提到的F10和F11也是这个意思的具体表现。这里再次强调这个思路的意思是,希望新手在遇到问题而困惑时这个思考的技巧可以帮助自己理顺思路,而不是仅仅把F10和F11的作用看成是两个按键对应的功能。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、密解密等。这使得开发者可以更专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、密解密等。这使得开发者可以更专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值