高效能编程的七个好习惯

上一篇 / 下一篇  2009-01-10 01:00:48 / 个人分类:测试相关

1.使用工具帮你找Bug,而不是人工找.

工具包括用单元测试, assert语句,代码测试容器.人工指用printdebugger一行一行跟踪.我们知道,编程中绝大部分时间是耗费在除bug.不同的人有不同的debug的方法.我个人比较喜欢”极限编程(XP)学派的主义,也就是说,代码未动,测试先行.

 

单元测试中的红棒绿棒(熟悉JUnit的读者知道我在说什么)一出现,哪里出了问题就一目了然.单元测试的另外一个好处在于增加写程序的自信.以前没用单元测试之前,每天晚上改代码改到很晚的时候脑子常常不灵活,把代码改错,然后第二天来还要重头弄.有了单元测试之后每天晚上保证测试全部过掉,这样心理踏实,睡觉也香,早晨也不忙,吃饭也棒.

 

一般的语言都有assert,但是很少有人用.其实assert是一个非常好的DEBUG工具, Cassert能够把哪一个文件哪一行出了错都告诉你.不过我一般会自己写一个这样的assert:

 

#define ASSERT(value, msg) if (!(value)) {fprinft(stderr, “At file %s, line %d: \n message: %s\n”, __FILE__, __LINE__, msg); exit(-1);}

 

这样的ASSERT可以带一个信息出来,比起原来只告诉你哪个文件哪一行更加有价值.

 

第三个是用容器帮你找Bug.这一点以C/C++程序最为突出,因为编译之后直接就是可执行代码,运行时的信息不像JavaPython这样有VM的语言容易得到.这时候,我推荐valgrind.这个工具能够把C/C++程序放到一个容器中执行,记下每一个内存访问.被这样的容器debug一下,基本上指针指飞了(Segmentation Fault)的情况几乎就没有了.想像一下是用GDB追踪非法指针和内存泄露方便,还是用容器告诉你哪一个指针非法,哪一个内存没释放方便 

 

2.选用自动化工具构建

 

gcc或者简单的IDE来编译和运行程序在编程初期是很快速的,可是越到后来,会越臃肿.在编译的时候,不同的参数,不同的目标,IDE/gcc里面每次都要设定.而且一般的IDE也不能做到自动解决依赖等高级方法.因此,最好的方法是用Ant或者Makefile管理项目.这方面教程很多,而且我估计编程的个个都知道.不管项目大小,注意频繁使用就是了.

 

自动化测试也有很多工具,特别是GUI和命令行测试的自动化,工具链都很完整.大公司里的程序员走这方面的流程都比较规范(我在西门子实习过),但是小一点的公司中,或者个人搞小项目的时候,就不一定想得起来了(大部分我见到的程序员就手工来测试).手工测试看上去快,但是要是积累的次数多了就比较浪费时间了.其实自动化测试工具的学习成本很低的,事半功倍.

 

3.买本小书做参考,而不是用Google.

 

这是大实话.我大三开始学Python的时候,语言特性并不熟悉,手头也没有书,因此常常连取个随机数都要上Google查一下库.我发现,不管网络多快,自己搜索技术多牛,还是没有手头一本书方便.后来打印了一个7页的标准库的cheatsheet,编程立即行云流水.我在实习的时候也观察到,大部分时候程序员不可能记住一个框架所有的API,所以他们要不等IDE几秒钟做代码补全,要不一边翻文档一边做.或许MSDN这些本地文档系统比查书快吧,但是用Google和网络搜索绝对比书慢.现在因为工作原因,常常要学一些新的语言,我做的第一件事情,就是把他的库接口的网页全部打印了下来.

 

4.用脚本语言开发原型

 

人月神话的作者Brooks:准备把第一版扔掉,因为第一版必然要被扔掉.这是大实话和真理.既然第一版要被扔掉,咱们就让第一版扔掉得越早越好.说白了就是,原型要快速的被开发.

 

所谓的快速原型开发,大致有两个捷径,第一是只做核心的功能,输入输出都是构造好的简单的例子.第二是只做最简单的情况,对于性能和健壮性什么的都不太考虑.这两点,恰好是脚本语言最擅长的.脚本语言擅长于用精简的几行构造出复杂的功能,并且语法很松散,潜在假设程序是正确的.

 

即使在代码编写阶段,一些功能的实现,也是要先写个简单的,再慢慢打磨成复杂的.脚本语言此时依然有用.比如我在用Java的时候,常常不确定一个函数返回的对象究竟某个属性是什么样的值.这时候我就会用Javabsh脚本写一行打印,而不会写一个复杂的out.println再编译再运行再把那行删除掉.当然,这几年很流行动态语言,原型和产品之间的差距已经变得很小了.

 

5.必要的时候,程序要使用清晰的,自我解释的文本文件作为日志输出.

 

不知道各位调试程序的时候是不是和我一样,看到不确定的和要跟踪的变量就直接插入一行print.我以前一直这样做,但是频繁的插入这样的打印会使得屏幕的输出很乱,不知道哪行是什么意思.一个更加好的办法是写一个日志函数,可以分也可以不分优先级,总之保证Debug的时候的输出以一种统一的,可管理的方式出现.这样,在最后发布稳定版本的时候,只需要简单的几行命令就可以从代码中剔除所有的日志打印行.

 

如果必然要输出日志,最好要分配一个单独的命令行参数,用来控制程序究竟输出不输出日志,输出哪些日志.一开始看上去这个是费时费力,越到后来日志越多的时候,就体会到方便之处:有时候你只想要某一类日志,可是其他的记录偏偏来捣乱.多加一个参数可以使得程序更加灵活,根本不需要去修改代码或者条件编译就能得到不同级别的程序日志.

 

日志和程序的输出结果一定要清晰且能自我解释,否则不如没有日志.我切身经历是这样的:几个月前,我一个程序跑了大约一天,最后输出了很大的日志和结果.但是很不幸的是,结果里只有数字,没有任何说明.我自己都忘了每一行是什么意思.而且更加麻烦的是程序的输出藏在重重判断和循环之内,使得根本没有办法分析这一行输出对应的输入是什么.于是,最终只能再次浪费一天的时间让程序再跑一次.?经过这次教训,我的程序日志和结果中插入了不少让人可读的内容.这样,即使程序丢失了,结果还是能够被人解读的.

 

更多的关于数据和程序结果要能自我解释的精彩论述,可参见More Programming Pearls第四章.

 

6.使用命令行小工具操控分析你的结果和代码,而不是用自己的眼睛和手.

 

我发现,人有一个固有的习惯,就是喜欢自己去”人工”,而不喜欢用工具.因为人工让人感觉工作更加刻苦,更加快,更加有控制感.比如说吧,上面我说的测试,我就不只一次见到为了测一个交互式的命令行,一个程序员宁愿老是每次打相同的三个命令,而不愿意用一个简单的expect.再比如说,面对长长的日志文件,我见到很多人都是用文本编辑器直接打开,用鼠标滚轮一行一行的往下翻,而不是使用grep.包括看网页,很多人从来不用查找功能,而是一行一行的往下瞄.包括打游戏也是,好的UI脚本(不是外挂)一大把,可是玩WoW的人很少用,都喜欢自己重复点鼠标.

 

别看上面说的这些好像程序员没有,其实我们常常陷入这个误区.举个简单的例子,一个python程序里面有十几个print函数,我们想把这些打印全部灭掉,一般人会打开文件慢慢瞄,稍微高级一点的用查找,找到了,用快捷键删掉整行.其实最好的方法根本都不要编辑器,应该用grep -v.或者sed,但是这样的方法极少会有人用的.我也是强迫自己无穷多次之后,才渐渐的用这套快速的方法.

 

7.程序能跑就是万岁.除非万不得已,尽量不要在性能上优化你的代码

 

Knuth名言: Premature optimization is the root of all evil. (提前优化是万恶之源).一般我们写代码的时候,不知不觉的就会觉得,哎呀,这样写效率不高,我要构造一个数据结构啥啥.随机访问一定要哈希表,排序一定上快排,查找一定要二分,强连通分量一定要用Tarjan算法,动规一定比穷举好等等,这些竞赛的时候极限情况下正确的论断其实在实际环境中并不重要,因为做编程的一开始关键是能跑,而不是跑得快.往往这么以优化,程序很难debug,倒是还要去翻算法导论和TAoCP看人家的二分怎么写的等等.

 

在程序能跑的情况下,优化也要特别小心.我曾经有一个程序,大约有90%的运算是查表,只有1%的是乘法,另外是一些判断和把插到的结果插入到一个集合中.我的查表是用的最土的list.index.按照正常的想法,应该把这个优化成哈希表.而实际上我用profile工具一看,才知道,原来是插入到一个集合的操作费时间,因为每次都需要extend,涉及到很多内存分配的操作.我做过非常多的profile测试,没有一次不出乎我预料的.程序运行时间总是在自己不认为浪费的地方被浪费掉.因此,就算万不得已优化,也务必要先做一下profiling.我喜欢python的地方就在于,他的profiling只需要一行语句就完成了,而且结果具体干净.其他的语言,至今没见到这么简单的profiling工具

 


TAG: 测试相关

 

评分:0

我来说两句

Open Toolbar