`
mqzhuang
  • 浏览: 185394 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论
阅读更多

程序员 在软件开发过程中离不开 编码 和调试,这两个过程也是相辅相成的,我就个人的体会来谈谈这两个方面。

 

编码篇:

 

1 ,程序为什么会有 bug ?其中一个比较关键的原因是程序员的主观因素造成的,比如编码习惯不好,粗心,不理解系统,系统太复杂没有想清楚设计,没有考虑到某些意外情况,单元测试不到位等等。

 

2 ,编码前想清楚设计的细节,对设计细节越清楚,编码越顺利,想清楚了后写代码,往往能体会到那种行云流水,一气呵成的感觉,并且代码的 bug 更少。

 

3 我 有种体会就是,当想清楚了自己的设计细节,在头脑最清楚的时候写好代码的框架,一气呵成把一个可以运行的最小功能代码写好,在第一次编译的时候,如果经过 很小的修改就顺利编译通过,再尝试运行,如果遇到运行时的几个问题也比较简单,很快解决,意味着这部分代码质量不错,以后这块代码出问题的可能性也会小很 多。

 

4 编码前最好想清楚自己的设计细节,至少要想好 80% 以上的设计细节,我通常会写一份简单的文档来描述自己的设计,文档不一定正式,但一定要写简洁清楚,发给其 pm 或是其他同事拍砖,如果他们能很快看懂并没有异议,证明这个方案还不错,要是同事提出了一些意见,一定要考虑他们的意见,多讨论权衡后决定如何做。

 

5 编码是软件开发中比较小的一部分,但无疑是很核心的一部分,也是程序员最喜欢的一部分工作,但花在编码上的时间一般不会太长,假设一个 5000 行左右的模块,我一般会用 3-5 天时间把整个模块的框架代码和基础功能代码一口气写完,写代码时真的很充实,完全处于“颠疯”状态。

 

6 看代码的时间远远超过了写代码的时间,写完一段代码要及时回想一下,站在高一层的角度来审视代码,就像在高中考试答题一样,及时检查可以用最小的代价第一时间出代码的问题。

 

7 当第一个已基本功能的模块完成时,第一遍审查自己的代码,加上一些必要的注释,编写单元测试代码,这部分工作很单调但很关键,完备的单元测试可以把 bug 扼杀在摇篮中。单元测试一般以最简单的方式编写,使用一个不错的单元测试框架就事半功倍,比如 gtest cppunit ,必要的时候写一些 mock 。我的单元测试代码不像项目代码那么正规,单元测试代码的重构做得比较少,要是某个模块有大的修改,就直接放弃那部分的测试代码,重新写新的测试代码。我写的单元测试代码一般会比项目代码多,经过自己亲手测试的代码比较放心。

 

8 ,尽量用最简单的方式实现功能,程序员有时候会随做经验的增加,把简单的问题复杂化,但开发中常常需要的是把复杂的问题简单化,用最简洁,最快的方式解决眼下的问题。

 

9 使用自己最熟悉的语言特性实现功能,不要使用费解或是不直观的语言特性。毕竟程序员不是语言专家,最简单的招式用到极致就是绝招。比如在我们的高性能分布式服务器系统中,虽然采用的是 C++ 开发,但我们主要使用的 C 的语言特性,不使用 STL boost ,我们只使用我们最熟悉,最可控的语言特性。服务器是需要长时间运行的,一个不可控的因素就可能导致系统崩溃。

 

10 程序有 bug 是难免的,但我们尽量让 bug 无处藏身,相信自己的开发水平,相信编译器,相信自己的单元测试,相信系统测试,相信压力测试,一步一步地, bug 真的无处藏身。

 

调试篇:

 

1 这里主要讨论开发的代码调试,没有源代码的的调试要借助比较多的额外工具,包含比较多的技巧性的东西,这个话题留着以后做进一步探讨。

 

2 在学习程序开发的初级阶段,最喜欢用的调试方法就是 " 暴力 "printf 方法,不是说这个方法不好,而是这个调试方法一般不会成为调试方法的首选,当进入正规的开发项目后,系统中都要求实现比较完善的 log 机制,根据 log 的不同级别,一般就能实现 bug 的初步定位。代码中实现完善的 log 机制对多线程程序的调试带来了便捷。

 

3 在 代码的的基本功能实现以后,第一时间审查代码和实现单元测试,确保单元测试通过,然后不断完善代码功能和添加相关的单元测试,每添加一个新功能都确保单元 测试通过,如果单元测试不能通过,第一步想到的就是检查最近修改的代码,由于是迭代开发的,每个阶段都保证了单元测试通过,所以查找一个新引入的 bug 是比较容易的。

 

4 在项目开发后期,系统进入连调阶段,如果你的单元测试做得比较到位,设计清晰简洁,那时候你就会看到其他人都忙着修改 bug ,你却很清闲。那时候你会体会到完备单元测试 + 迭代开发的好处。

 

5 我不太常用 gdb ,一般程序的 bug 在单元测试阶段构造一些简单的测试场景就修复了,并在程序中每个函数都做了充分的参数检查,程序出 core 的情况并不多,但程序发生段错误时, gdb 是一个比较方便的定位方法。

 

6 一般情况下,程序出错,首先看 log ,如果 log 比较完备,并对自己的代码比较熟悉,基本就能定位 bug 的地方了。如果没有头绪并且 bug 很容易复现,就采用 gdb 调试,使用 gdb 也需要有针对性,首先查看自己怀疑的对象,如果涉及到其他同事写的代码,在调试过程中可以熟悉代码的执行过程。一般简单的 bug ,凭着经验应该能很快定位。能反复重现的 bug gdb 查找是比较快的。

 

7 使用 gdb 调试比较复杂的多线程问题时,我比较喜欢用 gdb attach  <pid> 想象一下,一个程序直接跑就出错,但是放到gdb下就能得到正确的结果,好像故意在耍我们一样 这种方法的好处是能够使gdb对程序执行的影响最小,而且可以只接管程序中某一条我们所关心的线程,而其他线程不受影响 。如果 还没来得及attach线程就已经执行完或者 crash ,我们可以在线程中加入 sleep ()。

 

8 在做压力测试时要是系统崩溃,一般有几种方式来处理这种情况,最常见的一种情况就是 segmentation fault ,我们可以将程序运行在 gdb 中,一旦程序 crash gdb 可以看到最后的出错堆栈信息,但在 gdb 中会影响到程序的性能。第二种办法是查看程序的 core 文件,但程序消耗的内存很庞大时,生成 core 文件也需要很长时间。还有一种办法是使用信号来保存现场。 这样你可以晚上写个脚本让程序无限跑,早上起来你会发现程序停在出错的地方,这是很惬意的事情 ,我们只需要在信号的处理函数中添加一段循环 sleep 的代码就可以了。

 

9 内存问题调试采用 valgrind ,一般的内存错误都逃不出它的魔眼,但关键还是需要自己在开发过程中多注意,养成正确使用内存的好习惯,简单设计是防止内存问题的一个比较有效的方法。

 

10 更复杂的系统 bug 就需要用一些工具来辅助解决了,比如性能问题,我们会采用 oprofile ,或是 systemtap 来找到系统的热点。比如全异步,多并发,分布式的服务器程序,我们在试运行时可以采用内存 log 的方式,将系统的的 log 级别定为 DEBUG ,系统将 log 写入一个循环内存缓冲区,当系统出问题时可以看到系统最后那段时间的详细信息,方便调试。对已经在线上运行的系统的 bug 调试,调试手段真的有限,一般采用监测系统状态和系统的一些 stat 接口来间接定为系统是否运行正常,有时候也使用 systemtap 这样的工具对重点怀疑的对象进行监测。

分享到:
评论
1 楼 boyhailong 2013-03-31  
总结的很好,顶

相关推荐

Global site tag (gtag.js) - Google Analytics