一. 引言
八月上旬,深圳举办了一个讨论会,主题是"编写优质无错代码"。这个讨论
会吸引了深圳各大软件公司,通信公司的程序员,系统分析员参加,并在讨
论会后纷纷表示,这种讨论会很有实际价值,希望将这种形式的讨论会继续
下去,形成一个论坛,以提高大家的编程水平和交换有价值的信息资料。
这个活动的发起是从网络上开始的。我偶然看到了这个讨论会的论题,发生
了兴趣。本来周末的我一般是很懒的,没什么事情是不会出门的。而当我看
到这论题后,就给举办者发信表示愿意参加。于是,一个周六的下午,我就
坐在了讨论会的现场。参加完这个讨论后,我觉得有必要把其中的精华部分
写下来,和网络上的广大程序员共享,于是就有了这篇文章。
二.主题:
编写优质无错的代码---讨论会主题。
相信每个程序员都有这种希望,谁都不愿意自己写出来的代码在release之
后出错,需要不停的修改维护。但是,主持人提出了这样一个问题:"编写
优质无错代码是否必要?" 为什么呢?我稍微解释一下。在项目的时间很
紧张的时候,是按期完成任务重要,还是代码的稳定性,优质无错重要呢?
主持人提出的四个具体问题是:
1、编写优质无错的代码的代价是什么?
2、代码的质量重要还是编写效率重要?
3、在压力的情况下,你会牺牲质量来提高效率么?
4、编写优质无错的代码是否意味着效率的降低?
对于第一个问题,编写优质无错代码的代价当然是时间,不过随着编程人
员的经验逐渐丰富,所需要的时间也逐渐减少。
对于第二个问题,代码的质量比编写效率重要。当你花了1周时间写出来的
代码需要你花一个月或者更长的时间去debug, 去修改错误,这种效率的损
失是得不偿失的。
对于第三个问题, 这需要看项目经理或者产品经理的态度和专业精神了。
如果在一个专业的项目经理或者产品经理的指挥下,当然是首先保证质量其
次提高效率。而对于某些项目经理或者产品经理来说,按时完成任务是最重
要的,他们往往不在乎在软件发布之后花比开发时间长得多的时间去修改程
序,维护错误。因为,对于他们来说,首先是要完成任务,好给上级领导交
差,至于后期维护,就是另外一个任务了,维护花的时间多,正说明了他这
个项目的复杂性和难度。而对于开发人员来讲,所希望的则正好相反。开发
人员不喜欢花太多的时间在一个烂摊子上。所以,在讨论会上,大家纷纷表
示,应该让项目经理或者产品经理也来听一听这个讨论会:-)。
对于第四个问题,当然优质无错代码不是意味着效率的降低,而是正好相反
,对提高效率有很好的促进作用。一个版本发布之后,如果因为错误太多,
开发人员不得不去花很多时间修改bug, 甚至要从系统的体系结构方面去做
大的改动,重新编写部分代码,这种效率的降低才是更大,更不能承受的。
而且,花了太多的时间在老版本的维护上,必然影响到新版本的工程进度,
直接影响到整个产品线的质量和进度,严重的甚至会毁掉整个产品。
对于这一个主题,我的回答是,在时间允许的范围之内,尽量提高代码的质
量,不追求慢工出细活,不追求代码的100%无错,但是要保证99%以上的无
错。这样,在时间的压力下,在质量要求的束缚下,就要求程序员有一个良
好的习惯,和稳健的编程风格,以保证代码的优质无错。这就是第二个问题
:什么是编写优质无错的
代码的核心思想?优质无错是相对的,而不是绝对的。任何代码,都不可能
说是绝对无错的,但是在绝大部分情况下,是稳定的,强健的,优质的,无
错的。每次发布的时候,都会对上次的发布版本做若干修改,增强功能的同
时,也要修改若干bug。
那么,核心思想就是:
怎样才能自动地查出这个错误。
怎样才能避免这个错误。
三.编写优质无错代码的经验
在说了上面很多理论性的问题之后,来看一看具体问题。
先来看一看一个具体的题目:(我本人就是先在网上看了这个题目,才对这
个讨论会发生兴趣的)
题目1:
作为开发团队的一员,你需要实现一些库函数提供给其他人使用。假设你实
现的一个函数原型如下:
int DoSomeThing(char* pParam)
{
...
}
你们约定好参数pParam不能为NULL,但为了防止调用者错误传递NULL,你需
要在你的函数里做判断处理。
请问你会选择那种方式,并说明原因?
(a) if (!pParam)
return 0;
(b) if (!pParam)
return ERROR_PARAM;
(c) if (!pParam)
pParam = "";
...
(d) if (!pParam)
throw EXCEPTION_ERROR_PARAM;
(e) if (!pParam)
MessageBox(...);
(f) assert(!pParam);
(附加说明一点,基于目前开发人员技术分布情况和参与讨论会的人员的技
术分布情况,这次所列举的例子都是C/C++和Java方面的,不涉及到VB, PB,
Delphi等语言。不过对于这些程序员,讨论也是有借鉴作用的。)
关于这个问题,大概是所有的程序员都会遇到的。所以,在网上和讨论会上,
都发生了激烈的争论和意见交换。我大概把主要的几种观点记录了一下,列
举在下面:
1、选择f的理由
因为非NULL是约定,所以可以确定是调用者的问题,f可以明确地指出这一
点,防止错误扩散。
我的附加说明: 防止错误扩散的意思是,如果用其他方式,比如throw
exception的方式,这个异常不一定会在调用此函数的上一层被捕捉到,可
能会被继续抛出直到最上一层或者直到在某一层被catch到,这样的话,错误
就会距离发生地点很远,扩散开来。
这一观点,代表了一大部分的程序员的观点。
2、反对用f
不赞成assert, assert更重要的作用是程序体里面的一个注释, 在阅读程序
的时候起作用不能依赖他来检测错误, 很大程度上assert容易使使用者依赖
它本不应该依赖的东西。
这也代表了部分程序员的观点,认为assert是不可依赖的,而应该依赖于错
误检测,比如返回值或者异常。
3、另外一种观点
f和d都可取。如果没有系统开销的考虑,d则更好些。可以一举两得。如果没
人catch这个exception,其结果就跟f一样,按bug处理,dump core留下一
stack trace。如果有人catch,那就按运行错误处理......
但是返回一特初值表示错误,只是将错误上交,掩耳盗铃而已。最终总得有个
人assert,messagebox,throw exception,perror+exit,或别得什么的。既然已
经是约定,就干脆付起责任。
4、一种反对d的理由
不可用d, 这就像你用人,却不相信人一样,偏要try,catch防范他。其实那个错
是自己造成的,如果看到异常就容易不检讨自己。
5、关于观点3的支持意见