<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>温室小花.技术.博客 --纯粹的unix技术博客 &#187; vc++/vc#</title>
	<atom:link href="http://www.evanjiang.net.cn/archives/category/application-_developmen/vcvc/feed" rel="self" type="application/rss+xml" />
	<link>http://www.evanjiang.net.cn</link>
	<description>红颜弹指老，刹那芳华，与其天涯思君，恋恋不舍，莫若相忘于江湖！</description>
	<lastBuildDate>Sun, 05 Sep 2010 14:51:18 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>软件工程师必读：编程修养</title>
		<link>http://www.evanjiang.net.cn/archives/842.html</link>
		<comments>http://www.evanjiang.net.cn/archives/842.html#comments</comments>
		<pubDate>Fri, 20 Mar 2009 15:01:22 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[vc++/vc#]]></category>
		<category><![CDATA[技术感悟]]></category>
		<category><![CDATA[编程修养]]></category>

		<guid isPermaLink="false">http://www.evanjiang.net.cn/?p=842</guid>
		<description><![CDATA[<p>


 <p>什么是好的程序员？是不是懂得很多技术细节？还是懂底层编程？还是编程速度比较快？我觉得都不是。对于一些技术细节来说和底层的技术，只要看帮助，查资料就能找到，对于速度快，只要编得多也就熟能生巧。
我认为好的程序员应该有以下几方面的素质：
　1、有专研精神，勤学善问、举一反三。
　2、积极向上的态度，有创造性思维。
　3、与人积极交流沟通的能力，有团队精神。
　4、谦虚谨慎，戒骄戒燥。
5、写出的代码质量高。包括：代码的稳定、易读、规范、易维护、专业。
这些都是程序员的修养，这里我想谈谈“编程修养”，也就是上述中的第5点。我觉得，如果我要了解一个作者，我会看他所写的小说，如果我要了解一个画家，我会看他所画的图画，如果我要了解一个工人，我会看他所做出来的产品，同样，如果我要了解一个程序员，我想首先我最想看的就是他的程序代码，程序代码可以看出一个程序员的素质和修养，程序就像一个作品，有素质有修养的程序员的作品必然是一图精美的图画，一首美妙的歌曲，一本赏心悦目的小说。
我看过许多程序，没有注释，没有缩进，胡乱命名的变量名，等等，等等，我把这种人统称为没有修养的程序，这种程序员，是在做创造性的工作吗？不，完全就是在搞破坏，他们与其说是在编程，还不如说是在对源程序进行“加密”，这种程序员，见一个就应该开除一个，因为他编的程序所创造的价值，远远小于需要在上面进行维护的价值。
程序员应该有程序员的修养，那怕再累，再没时间，也要对自己的程序负责。我宁可要那种动作慢，技术一般，但有良好的写程序风格的程序员，也不要那种技术强、动作快的“搞破坏”的程序员。有句话叫“字如其人”，我想从程序上也能看出一个程序员的优劣。因为，程序是程序员的作品，作品的好坏直截关系到程序员的声誉和素质。而“修养”好的程序员一定能做出好的程序和软件。
有个成语叫“独具匠心”，意思是做什么都要做得很专业，很用心，如果你要做一个“匠”，也就是造诣高深的人，那么，从一件很简单的作品上就能看出你有没有“匠”的特性，我觉得做一个程序员不难，但要做一个“程序匠”就不简单。编程序很简单，但编出有质量的程序就难。
我在这里不讨论过深的技术，我只想在一些容易让人忽略的东西上说一说，虽然这些东西可能很细微，但如果你不注意这些细微之处的话，那么他将会极大的影响你的整个软件质量，以及整个软件程的实施，所谓“千里之堤，毁于蚁穴”。
“细微之处见真功”，真正能体现一个程序的功底恰恰在这些细微之处。
这就是程序员的&#8211;编程修养。我总结了在用C/C++语言（主要是C语言）进行程序写作上的三十二个“修养”，通过这些，你可以写出质量高的程序，同时也会让看你程序的人渍渍称道，那些看过你程序的人一定会说：“这个人的编程修养不错”。
　　&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;
　　　　
　　　　01、版权和版本
　　　　02、缩进、空格、换行、空行、对齐
　　　　03、程序注释
　　　　04、函数的[in][out]参数
　　　　05、对系统调用的返回进行判断
　　　　06、if 语句对出错的处理
　　　　07、头文件中的#ifndef
　　　　08、在堆上分配内存
　　　　09、变量的初始化
　　　　10、h和c文件的使用
　　　　11、出错信息的处理
　　　　12、常用函数和循环语句中的被计算量
　　　　13、函数名和变量名的命名
　　　　14、函数的传值和传指针
　　　　15、修改别人程序的修养
　　　　16、把相同或近乎相同的代码形成函数和宏
　　　　17、表达式中的括号
　　　　18、函数参数中的const
　　　　19、函数的参数个数
　　　　20、函数的返回类型，不要省略
　　　　21、goto语句的使用
　　　　22、宏的使用
　　　　23、static的使用
　　　　24、函数中的代码尺寸
　　　　25、typedef的使用
　　　　26、为常量声明宏
　　　　27、不要为宏定义加分号
　　　　28、&#124;&#124;和&#038;&#038;的语句执行顺序
　　　　29、尽量用for而不是while做循环
　　　　30、请sizeof类型而不是变量
　　　　31、不要忽略Warning
　　　　32、书写Debug版和Release版的程序
　　&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</p>
<p>1、版权和版本
&#8212;&#8212;-
好的程序员会给自己的每个函数，每个文件，都注上版权和版本。
对于C/C++的文件，文件头应该有类似这样的注释：
/************************************************************************
*
*　 文件名：network.c
*
*　 文件描述：网络通讯函数集
*
*　 创建人： Hao Chen, 2003年2月3日
*
*　 版本号：1.0
*
*　 修改记录：
*
************************************************************************/
而对于函数来说，应该也有类似于这样的注释：
/*================================================================
*
* 函 数 名：XXX
*
* 参　　数：
*
*　　　　type name [IN] : descripts
*
* 功能描述:
*
*　　　　&#8230;&#8230;&#8230;&#8230;..
*
* 返 回 值：成功TRUE，失败FALSE
*
* 抛出异常：
*
* 作　　者：ChenHao 2003/4/2
*
================================================================*/
这样的描述可以让人对一个函数，一个文件有一个总体的认识，对代码的易读性和易维护性有很大的好处。这是好的作品产生的开始。</p>
<p>2、缩进、空格、换行、空行、对齐
&#8212;&#8212;&#8212;&#8212;&#8212;-
i) 缩进应该是每个程序都会做的，只要学程序过程序就应该知道这个，但是我仍然看过不缩进的程序，或是乱缩进的程序，如果你的公司还有写程序不缩进的程序员，请毫不犹豫的开除他吧，并以破坏源码罪起诉他，还要他赔偿读过他程序的人的精神损失费。缩进，这是不成文规矩，我再重提一下吧，一个缩进一般是一个TAB 键或是4个空格。（最好用TAB键）
ii) 空格。空格能给程序代来什么损失吗？没有，有效的利用空格可以让你的程序读进来更加赏心悦目。而不一堆表达式挤在一起。看看下面的代码：
　　ha=(ha*128+*key++)%tabPtr->size;
　　ha = ( ha * 128 + *key++ ) % tabPtr->size;
　　有空格和没有空格的感觉不一样吧。一般来说，语句中要在各个操作符间加空格，函数调用时，要以各个参数间加空格。如下面这种加空格的和不加的：
　　
if ((hProc=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid))==NULL){
}
if ( ( hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) ) == NULL ){
}
iii) 换行。不要把语句都写在一行上，这样很不好。如：
　　for(i=0;i &#180;z&#180; ) ) [...]]]></description>
			<content:encoded><![CDATA[<p style="float: left;margin: 4px;"><script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 160x600, 创建于 10-2-7 */
google_ad_slot = "8970910006";
google_ad_width = 160;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script></p> <p>什么是好的程序员？是不是懂得很多技术细节？还是懂底层编程？还是编程速度比较快？我觉得都不是。对于一些技术细节来说和底层的技术，只要看帮助，查资料就能找到，对于速度快，只要编得多也就熟能生巧。<br />
我认为好的程序员应该有以下几方面的素质：<br />
　1、有专研精神，勤学善问、举一反三。<br />
　2、积极向上的态度，有创造性思维。<br />
　3、与人积极交流沟通的能力，有团队精神。<br />
　4、谦虚谨慎，戒骄戒燥。<br />
5、写出的代码质量高。包括：代码的稳定、易读、规范、易维护、专业。<br />
这些都是程序员的修养，这里我想谈谈“编程修养”，也就是上述中的第5点。我觉得，如果我要了解一个作者，我会看他所写的小说，如果我要了解一个画家，我会看他所画的图画，如果我要了解一个工人，我会看他所做出来的产品，同样，如果我要了解一个程序员，我想首先我最想看的就是他的程序代码，程序代码可以看出一个程序员的素质和修养，程序就像一个作品，有素质有修养的程序员的作品必然是一图精美的图画，一首美妙的歌曲，一本赏心悦目的小说。<br />
我看过许多程序，没有注释，没有缩进，胡乱命名的变量名，等等，等等，我把这种人统称为没有修养的程序，这种程序员，是在做创造性的工作吗？不，完全就是在搞破坏，他们与其说是在编程，还不如说是在对源程序进行“加密”，这种程序员，见一个就应该开除一个，因为他编的程序所创造的价值，远远小于需要在上面进行维护的价值。<br />
程序员应该有程序员的修养，那怕再累，再没时间，也要对自己的程序负责。我宁可要那种动作慢，技术一般，但有良好的写程序风格的程序员，也不要那种技术强、动作快的“搞破坏”的程序员。有句话叫“字如其人”，我想从程序上也能看出一个程序员的优劣。因为，程序是程序员的作品，作品的好坏直截关系到程序员的声誉和素质。而“修养”好的程序员一定能做出好的程序和软件。<br />
有个成语叫“独具匠心”，意思是做什么都要做得很专业，很用心，如果你要做一个“匠”，也就是造诣高深的人，那么，从一件很简单的作品上就能看出你有没有“匠”的特性，我觉得做一个程序员不难，但要做一个“程序匠”就不简单。编程序很简单，但编出有质量的程序就难。<br />
我在这里不讨论过深的技术，我只想在一些容易让人忽略的东西上说一说，虽然这些东西可能很细微，但如果你不注意这些细微之处的话，那么他将会极大的影响你的整个软件质量，以及整个软件程的实施，所谓“千里之堤，毁于蚁穴”。<br />
“细微之处见真功”，真正能体现一个程序的功底恰恰在这些细微之处。<br />
这就是程序员的&#8211;编程修养。我总结了在用C/C++语言（主要是C语言）进行程序写作上的三十二个“修养”，通过这些，你可以写出质量高的程序，同时也会让看你程序的人渍渍称道，那些看过你程序的人一定会说：“这个人的编程修养不错”。<br />
　　&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
　<span id="more-842"></span>　　　<br />
　　　　01、版权和版本<br />
　　　　02、缩进、空格、换行、空行、对齐<br />
　　　　03、程序注释<br />
　　　　04、函数的[in][out]参数<br />
　　　　05、对系统调用的返回进行判断<br />
　　　　06、if 语句对出错的处理<br />
　　　　07、头文件中的#ifndef<br />
　　　　08、在堆上分配内存<br />
　　　　09、变量的初始化<br />
　　　　10、h和c文件的使用<br />
　　　　11、出错信息的处理<br />
　　　　12、常用函数和循环语句中的被计算量<br />
　　　　13、函数名和变量名的命名<br />
　　　　14、函数的传值和传指针<br />
　　　　15、修改别人程序的修养<br />
　　　　16、把相同或近乎相同的代码形成函数和宏<br />
　　　　17、表达式中的括号<br />
　　　　18、函数参数中的const<br />
　　　　19、函数的参数个数<br />
　　　　20、函数的返回类型，不要省略<br />
　　　　21、goto语句的使用<br />
　　　　22、宏的使用<br />
　　　　23、static的使用<br />
　　　　24、函数中的代码尺寸<br />
　　　　25、typedef的使用<br />
　　　　26、为常量声明宏<br />
　　　　27、不要为宏定义加分号<br />
　　　　28、||和&#038;&#038;的语句执行顺序<br />
　　　　29、尽量用for而不是while做循环<br />
　　　　30、请sizeof类型而不是变量<br />
　　　　31、不要忽略Warning<br />
　　　　32、书写Debug版和Release版的程序<br />
　　&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</p>
<p>1、版权和版本<br />
&#8212;&#8212;-<br />
好的程序员会给自己的每个函数，每个文件，都注上版权和版本。<br />
对于C/C++的文件，文件头应该有类似这样的注释：<br />
/************************************************************************<br />
*<br />
*　 文件名：network.c<br />
*<br />
*　 文件描述：网络通讯函数集<br />
*<br />
*　 创建人： Hao Chen, 2003年2月3日<br />
*<br />
*　 版本号：1.0<br />
*<br />
*　 修改记录：<br />
*<br />
************************************************************************/<br />
而对于函数来说，应该也有类似于这样的注释：<br />
/*================================================================<br />
*<br />
* 函 数 名：XXX<br />
*<br />
* 参　　数：<br />
*<br />
*　　　　type name [IN] : descripts<br />
*<br />
* 功能描述:<br />
*<br />
*　　　　&#8230;&#8230;&#8230;&#8230;..<br />
*<br />
* 返 回 值：成功TRUE，失败FALSE<br />
*<br />
* 抛出异常：<br />
*<br />
* 作　　者：ChenHao 2003/4/2<br />
*<br />
================================================================*/<br />
这样的描述可以让人对一个函数，一个文件有一个总体的认识，对代码的易读性和易维护性有很大的好处。这是好的作品产生的开始。</p>
<p>2、缩进、空格、换行、空行、对齐<br />
&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
i) 缩进应该是每个程序都会做的，只要学程序过程序就应该知道这个，但是我仍然看过不缩进的程序，或是乱缩进的程序，如果你的公司还有写程序不缩进的程序员，请毫不犹豫的开除他吧，并以破坏源码罪起诉他，还要他赔偿读过他程序的人的精神损失费。缩进，这是不成文规矩，我再重提一下吧，一个缩进一般是一个TAB 键或是4个空格。（最好用TAB键）<br />
ii) 空格。空格能给程序代来什么损失吗？没有，有效的利用空格可以让你的程序读进来更加赏心悦目。而不一堆表达式挤在一起。看看下面的代码：<br />
　　ha=(ha*128+*key++)%tabPtr->size;<br />
　　ha = ( ha * 128 + *key++ ) % tabPtr->size;<br />
　　有空格和没有空格的感觉不一样吧。一般来说，语句中要在各个操作符间加空格，函数调用时，要以各个参数间加空格。如下面这种加空格的和不加的：<br />
　　<br />
if ((hProc=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid))==NULL){<br />
}<br />
if ( ( hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) ) == NULL ){<br />
}<br />
iii) 换行。不要把语句都写在一行上，这样很不好。如：<br />
　　for(i=0;i<len;i++) if((a<&acute;0&acute;||a>&acute;9&acute;)&#038;&#038;(a<&acute;a&acute;||a>&acute;z&acute;)) break;<br />
　　<br />
　　我拷，这种即无空格，又无换行的程序在写什么啊？加上空格和换行吧。　　<br />
　　<br />
　　for ( i=0; i<len; i++) {<br />
　　　　if ( ( a < &acute;0&acute; || a > &acute;9&acute; ) &#038;&#038;<br />
　　　　　　 ( a < &acute;a&acute; || a > &acute;z&acute; ) ) {<br />
　　　　　　break;<br />
　　　　}<br />
　　}<br />
　　好多了吧？有时候，函数参数多的时候，最好也换行，如：<br />
　　CreateProcess(<br />
　　　　　　　　　NULL,<br />
　　　　　　　　　cmdbuf,<br />
　　　　　　　　　NULL,<br />
　　　　　　　　　NULL,<br />
　　　　　　　　　bInhH,<br />
　　　　　　　　　dwCrtFlags,<br />
　　　　　　　　　envbuf,<br />
　　　　　　　　　NULL,<br />
　　　　　　　　　&#038;siStartInfo,<br />
　　　　　　　　　&#038;prInfo<br />
　　　　　　　　 );<br />
　　条件语句也应该在必要时换行：<br />
　　<br />
　　if ( ch >= &acute;0&acute; || ch <= &acute;9&acute; ||<br />
　　　　 ch >= &acute;a&acute; || ch <= &acute;z&acute; ||<br />
　　　　 ch >= &acute;A&acute; || ch <= &acute;Z&acute; )<br />
　　　　　　　　　<br />
iv) 空行。不要不加空行，空行可以区分不同的程序块，程序块间，最好加上空行。如：<br />
　　HANDLE hProcess;<br />
　　PROCESS_T procInfo;<br />
　　/* open the process handle */<br />
　　if((hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)) == NULL)<br />
　　{<br />
　　　　return LSE_MISC_SYS;<br />
　　}<br />
　　memset(&#038;procInfo, 0, sizeof(procInfo));<br />
　　procInfo.idProc = pid;<br />
　　procInfo.hdProc = hProcess;<br />
　　procInfo.misc |= MSCAVA_PROC;<br />
　　return(0);<br />
　　　　　　　　　<br />
v) 对齐。用TAB键对齐你的一些变量的声明或注释，一样会让你的程序好看一些。如：<br />
typedef struct _pt_man_t_ {<br />
　　int　　 numProc;　　/* Number of processes　　　　　　　　 */<br />
　　int　　 maxProc;　　/* Max Number of processes　　　　　　 */<br />
　　int　　 numEvnt;　　/* Number of events　　　　　　　　　　*/<br />
　　int　　 maxEvnt;　　/* Max Number of events　　　　　　　　*/<br />
　　HANDLE* pHndEvnt;　 /* Array of events　　　　　　　　　　 */<br />
　　DWORD　 timeout;　　/* Time out interval　　　　　　　　　 */<br />
　　HANDLE　hPipe;　　　/* Namedpipe　　　　　　　　　　　　　 */<br />
　　TCHAR　 usr[MAXUSR];/* User name of the process　　　　　　*/<br />
　　int　　 numMsg;　　 /* Number of Message　　　　　　　　　 */<br />
　　int　　 Msg[MAXMSG];/* Space for intro process communicate */<br />
} PT_MAN_T;<br />
怎么样？感觉不错吧。<br />
这里主要讲述了如果写出让人赏心悦目的代码，好看的代码会让人的心情愉快，读起代码也就不累，工整、整洁的程序代码，通常更让人欢迎，也更让人称道。现在的硬盘空间这么大，不要让你的代码挤在一起，这样它们会抱怨你虐待它们的。好了，用“缩进、空格、换行、空行、对齐”装饰你的代码吧，让他们从没有秩序的土匪中变成一排排整齐有秩序的正规部队吧。</p>
<p>　　　　　　　　　<br />
3、程序注释<br />
------<br />
养成写程序注释的习惯，这是每个程序员所必须要做的工作。我看过那种几千行，却居然没有一行注释的程序。这就如同在公路上驾车却没有路标一样。用不了多久，连自己都不知道自己的意图了，还要花上几倍的时间才看明白，这种浪费别人和自己的时间的人，是最为可耻的人。<br />
是的，你也许会说，你会写注释，真的吗？注释的书写也能看出一个程序员的功底。一般来说你需要至少写这些地方的注释：文件的注释、函数的注释、变量的注释、算法的注释、功能块的程序注释。主要就是记录你这段程序是干什么的？你的意图是什么？你这个变量是用来做什么的？等等。<br />
不要以为注释好写，有一些算法是很难说或写出来的，只能意会，我承认有这种情况的时候，但你也要写出来，正好可以训练一下自己的表达能力。而表达能力正是那种闷头搞技术的技术人员最缺的，你有再高的技术，如果你表达能力不行，你的技术将不能得到充分的发挥。因为，这是一个团队的时代。<br />
好了，说几个注释的技术细节：<br />
i) 对于行注释（“//”）比块注释（“/* */”）要好的说法，我并不是很同意。因为一些老版本的C编译器并不支持行注释，所以为了你的程序的移植性，请你还是尽量使用块注释。<br />
ii) 你也许会为块注释的不能嵌套而不爽，那么你可以用预编译来完成这个功能。使用“#if 0”和“#endif”括起来的代码，将不被编译，而且还可以嵌套。</p>
<p>4、函数的[in][out]参数<br />
-----------<br />
我经常看到这样的程序：<br />
FuncName(char* str)<br />
{<br />
　　int len = strlen(str);<br />
　　.....<br />
}<br />
char*<br />
GetUserName(struct user* pUser)<br />
{<br />
　　return pUser->name;<br />
}<br />
不！请不要这样做。<br />
你应该先判断一下传进来的那个指针是不是为空。如果传进来的指针为空的话，那么，你的一个大的系统就会因为这一个小的函数而崩溃。一种更好的技术是使用断言（assert），这里我就不多说这些技术细节了。当然，如果是在C++中，引用要比指针好得多，但你也需要对各个参数进行检查。<br />
写有参数的函数时，首要工作，就是要对传进来的所有参数进行合法性检查。而对于传出的参数也应该进行检查，这个动作当然应该在函数的外部，也就是说，调用完一个函数后，应该对其传出的值进行检查。<br />
当然，检查会浪费一点时间，但为了整个系统不至于出现“非法操作”或是“Core Dump”的系统级的错误，多花这点时间还是很值得的。</p>
<p>5、对系统调用的返回进行判断<br />
&#8212;&#8212;&#8212;&#8212;&#8211;<br />
继续上一条，对于一些系统调用，比如打开文件，我经常看到，许多程序员对fopen返回的指针不做任何判断，就直接使用了。然后发现文件的内容怎么也读出不，或是怎么也写不进去。还是判断一下吧：<br />
　　fp = fopen(&#8220;log.txt&#8221;, &#8220;a&#8221;);<br />
　　if ( fp == NULL ){<br />
　　　　printf(&#8220;Error: open file errorn&#8221;);<br />
　　　　return FALSE;<br />
　　}<br />
其它还有许多啦，比如：socket返回的socket号，malloc返回的内存。请对这些系统调用返回的东西进行判断。</p>
<p>6、if 语句对出错的处理<br />
&#8212;&#8212;&#8212;&#8211;<br />
我看见你说了，这有什么好说的。还是先看一段程序代码吧。<br />
　　if ( ch >= &acute;0&acute; &#038;&#038; ch <= &acute;9&acute; ){<br />
　　　　/* 正常处理代码 */<br />
　　}else{<br />
　　　　/* 输出错误信息 */<br />
　　　　printf("error ......n");<br />
　　　　return ( FALSE );<br />
　　}<br />
这种结构很不好，特别是如果“正常处理代码”很长时，对于这种情况，最好不要用else。先判断错误，如：<br />
　　if ( ch < &acute;0&acute; || ch > &acute;9&acute; ){<br />
　　　　/* 输出错误信息 */<br />
　　　　printf(&#8220;error &#8230;&#8230;n&#8221;);<br />
　　　　return ( FALSE );<br />
　　}<br />
　　<br />
　　/* 正常处理代码 */<br />
　　&#8230;&#8230;</p>
<p>这样的结构，不是很清楚吗？突出了错误的条件，让别人在使用你的函数的时候，第一眼就能看到不合法的条件，于是就会更下意识的避免。</p>
<p>7、头文件中的#ifndef<br />
&#8212;&#8212;&#8212;-<br />
千万不要忽略了头件的中的#ifndef，这是一个很关键的东西。比如你有两个C文件，这两个C文件都include了同一个头文件。而编译时，这两个C文件要一同编译成一个可运行文件，于是问题来了，大量的声明冲突。<br />
还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用，你都要加上这个。一般格式是这样的：<br />
　　#ifndef　<标识><br />
　　#define <标识><br />
　　<br />
　　&#8230;&#8230;<br />
　　&#8230;&#8230;<br />
　　<br />
　　#endif<br />
　　<br />
<标识>在理论上来说可以是自由命名的，但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写，前后加下划线，并把文件名中的“.”也变成下划线，如：stdio.h<br />
　　#ifndef _STDIO_H_<br />
　　#define _STDIO_H_<br />
　　<br />
　　&#8230;&#8230;<br />
　　<br />
　　#endif<br />
　　<br />
（BTW：预编译有多很有用的功能。你会用预编译吗？）　　<br />
　　</p>
<p>8、在堆上分配内存<br />
&#8212;&#8212;&#8212;<br />
可能许多人对内存分配上的“栈 stack”和“堆 heap”还不是很明白。包括一些科班出身的人也不明白这两个概念。我不想过多的说这两个东西。简单的来讲，stack上分配的内存系统自动释放，heap上分配的内存，系统不释放，哪怕程序退出，那一块内存还是在那里。stack一般是静态分配内存，heap上一般是动态分配内存。<br />
由malloc系统函数分配的内存就是从堆上分配内存。从堆上分配的内存一定要自己释放。用free释放，不然就是术语&#8211;“内存泄露”（或是“内存漏洞 ”）&#8211; Memory Leak。于是，系统的可分配内存会随malloc越来越少，直到系统崩溃。还是来看看“栈内存”和“堆内存”的差别吧。<br />
　　栈内存分配<br />
　　&#8212;&#8211;<br />
　　char*<br />
　　AllocStrFromStack()<br />
　　{<br />
　　　　char pstr[100];<br />
　　　　return pstr;<br />
　　}<br />
　　<br />
　　<br />
　　堆内存分配<br />
　　&#8212;&#8211;<br />
　　char*<br />
　　AllocStrFromHeap(int len)<br />
　　{<br />
　　　　char *pstr;<br />
　　　　<br />
　　　　if ( len <= 0 ) return NULL;<br />
　　　　return ( char* ) malloc( len );<br />
　　}<br />
对于第一个函数，那块pstr的内存在函数返回时就被系统释放了。于是所返回的char*什么也没有。而对于第二个函数，是从堆上分配内存，所以哪怕是程序退出时，也不释放，所以第二个函数的返回的内存没有问题，可以被使用。但一定要调用free释放，不然就是Memory Leak！<br />
在堆上分配内存很容易造成内存泄漏，这是C/C++的最大的“克星”，如果你的程序要稳定，那么就不要出现Memory Leak。所以，我还是要在这里千叮咛万嘱付，在使用malloc系统函数（包括calloc，realloc）时千万要小心。<br />
记得有一个UNIX上的服务应用程序，大约有几百的C文件编译而成，运行测试良好，等使用时，每隔三个月系统就是down一次，搞得许多人焦头烂额，查不出问题所在。只好，每隔两个月人工手动重启系统一次。出现这种问题就是Memery Leak在做怪了，在C/C++中这种问题总是会发生，所以你一定要小心。一个Rational的检测工作--Purify，可以帮你测试你的程序有没有内存泄漏。<br />
我保证，做过许多C/C++的工程的程序员，都会对malloc或是new有些感冒。当你什么时候在使用malloc和new时，有一种轻度的紧张和惶恐的感觉时，你就具备了这方面的修养了。<br />
　　<br />
对于malloc和free的操作有以下规则：<br />
1) 配对使用，有一个malloc，就应该有一个free。（C++中对应为new和delete）<br />
2) 尽量在同一层上使用，不要像上面那种，malloc在函数中，而free在函数外。最好在同一调用层上使用这两个函数。<br />
3) malloc分配的内存一定要初始化。free后的指针一定要设置为NULL。　　<br />
注：虽然现在的操作系统（如：UNIX和Win2k/NT）都有进程内存跟踪机制，也就是如果你有没有释放的内存，操作系统会帮你释放。但操作系统依然不会释放你程序中所有产生了Memory Leak的内存，所以，最好还是你自己来做这个工作。（有的时候不知不觉就出现Memory Leak了，而且在几百万行的代码中找无异于海底捞针，Rational有一个工具叫Purify，可能很好的帮你检查程序中的Memory Leak）</p>
<p>9、变量的初始化<br />
--------<br />
接上一条，变量一定要被初始化再使用。C/C++编译器在这个方面不会像JAVA一样帮你初始化，这一切都需要你自己来，如果你使用了没有初始化的变量，结果未知。好的程序员从来都会在使用变量前初始化变量的。如：<br />
　　1) 对malloc分配的内存进行memset清零操作。（可以使用calloc分配一块全零的内存）<br />
　　2) 对一些栈上分配的struct或数组进行初始化。（最好也是清零）<br />
　　<br />
不过话又说回来了，初始化也会造成系统运行时间有一定的开销，所以，也不要对所有的变量做初始化，这个也没有意义。好的程序员知道哪些变量需要初始化，哪些则不需要。如：以下这种情况，则不需要。<br />
　　　　<br />
　　　　char *pstr;　/* 一个字符串 */<br />
　　　　pstr = ( char* ) malloc( 50 );<br />
　　　　if ( pstr == NULL ) exit(0);<br />
　　　　strcpy( pstr, "Hello Wrold" );<br />
但如果是下面一种情况，最好进行内存初始化。（指针是一个危险的东西，一定要初始化）<br />
　　　　char **pstr;　/* 一个字符串数组 */<br />
　　　　pstr = ( char** ) malloc( 50 );<br />
　　　　if ( pstr == NULL ) exit(0);<br />
　　　　<br />
　　　　/* 让数组中的指针都指向NULL */<br />
　　　　memset( pstr, 0, 50*sizeof(char*) );<br />
　　　　<br />
而对于全局变量，和静态变量，一定要声明时就初始化。因为你不知道它第一次会在哪里被使用。所以使用前初始这些变量是比较不现实的，一定要在声明时就初始化它们。如：<br />
　　Links *plnk = NULL;　/* 对于全局变量plnk初始化为NULL */</p>
<p>10、h和c文件的使用<br />
---------<br />
H文件和C文件怎么用呢？一般来说，H文件中是declare（声明），C文件中是define（定义）。因为C文件要编译成库文件（Windows下是.obj/.lib，UNIX下是.o/.a），如果别人要使用你的函数，那么就要引用你的H文件，所以，H文件中一般是变量、宏定义、枚举、结构和函数接口的声明，就像一个接口说明文件一样。而C文件则是实现细节。<br />
H文件和C文件最大的用处就是声明和实现分开。这个特性应该是公认的了，但我仍然看到有些人喜欢把函数写在H文件中，这种习惯很不好。（如果是C++话，对于其模板函数，在VC中只有把实现和声明都写在一个文件中，因为VC不支持export关键字）。而且，如果在H文件中写上函数的实现，你还得在 makefile中把头文件的依赖关系也加上去，这个就会让你的makefile很不规范。<br />
最后，有一个最需要注意的地方就是：带初始化的全局变量不要放在H文件中！<br />
例如有一个处理错误信息的结构：<br />
　　char* errmsg[] = {<br />
　　　　/* 0 */　　　 "No error",　　　　　　　　<br />
　　　　/* 1 */　　　 "Open file error",　　　　<br />
　　　　/* 2 */　　　 "Failed in sending/receiving a message",　<br />
　　　　/* 3 */　　　 "Bad arguments",　<br />
　　　　/* 4 */　　　 "Memeroy is not enough",<br />
　　　　/* 5 */　　　 "Service is down; try later",<br />
　　　　/* 6 */　　　 "Unknow information",<br />
　　　　/* 7 */　　　 "A socket operation has failed",<br />
　　　　/* 8 */　　　 "Permission denied",<br />
　　　　/* 9 */　　　 "Bad configuration file format",　<br />
　　　　/* 10 */　　　"Communication time out",<br />
　　　　......<br />
　　　　......<br />
　　};<br />
　　<br />
请不要把这个东西放在头文件中，因为如果你的这个头文件被5个函数库（.lib或是.a）所用到，于是他就被链接在这5个.lib或.a中，而如果你的一个程序用到了这5个函数库中的函数，并且这些函数都用到了这个出错信息数组。那么这份信息将有5个副本存在于你的执行文件中。如果你的这个errmsg很大的话，而且你用到的函数库更多的话，你的执行文件也会变得很大。<br />
正确的写法应该把它写到C文件中，然后在各个需要用到errmsg的C文件头上加上 extern char* errmsg[]; 的外部声明，让编译器在链接时才去管他，这样一来，就只会有一个errmsg存在于执行文件中，而且，这样做很利于封装。<br />
我曾遇到过的最疯狂的事，就是在我的目标文件中，这个errmsg一共有112个副本，执行文件有8M左右。当我把errmsg放到C文件中，并为一千多个C文件加上了extern的声明后，所有的函数库文件尺寸都下降了20%左右，而我的执行文件只有5M了。一下子少了3M啊。<br />
〔 备注 〕<br />
-----<br />
有朋友对我说，这个只是一个特例，因为，如果errmsg在执行文件中存在多个副本时，可以加快程序运行速度，理由是errmsg的多个复本会让系统的内存换页降低，达到效率提升。像我们这里所说的errmsg只有一份，当某函数要用errmsg时，如果内存隔得比较远，会产生换页，反而效率不高。<br />
这个说法不无道理，但是一般而言，对于一个比较大的系统，errmsg是比较大的，所以产生副本导致执行文件尺寸变大，不仅增加了系统装载时间，也会让一个程序在内存中占更多的页面。而对于errmsg这样数据，一般来说，在系统运行时不会经常用到，所以还是产生的内存换页也就不算频繁。权衡之下，还是只有一份errmsg的效率高。即便是像logmsg这样频繁使用的的数据，操作系统的内存调度算法会让这样的频繁使用的页面常驻于内存，所以也就不会出现内存换页问题了<br />
11、出错信息的处理<br />
---------<br />
你会处理出错信息吗？哦，它并不是简单的输出。看下面的示例：<br />
　　if ( p == NULL ){<br />
　　　　printf ( "ERR: The pointer is NULLn" );<br />
　　}<br />
　　<br />
告别学生时代的编程吧。这种编程很不利于维护和管理，出错信息或是提示信息，应该统一处理，而不是像上面这样，写成一个“硬编码”。第10条对这方面的处理做了一部分说明。如果要管理错误信息，那就要有以下的处理：<br />
　　/* 声明出错代码 */<br />
　　#define　　 ERR_NO_ERROR　　0　/* No error　　　　　　　　 */<br />
　　#define　　 ERR_OPEN_FILE　 1　/* Open file error　　　　　*/<br />
　　#define　　 ERR_SEND_MESG　 2　/* sending a message error　*/<br />
　　#define　　 ERR_BAD_ARGS　　3　/* Bad arguments　　　　　　*/<br />
　　#define　　 ERR_MEM_NONE　　4　/* Memeroy is not enough　　*/<br />
　　#define　　 ERR_SERV_DOWN　 5　/* Service down try later　 */<br />
　　#define　　 ERR_UNKNOW_INFO 6　/* Unknow information　　　 */<br />
　　#define　　 ERR_SOCKET_ERR　7　/* Socket operation failed　*/<br />
　　#define　　 ERR_PERMISSION　8　/* Permission denied　　　　*/<br />
　　#define　　 ERR_BAD_FORMAT　9　/* Bad configuration file　 */<br />
　　#define　　 ERR_TIME_OUT　 10　/* Communication time out　 */<br />
　　<br />
　　/* 声明出错信息 */<br />
　　char* errmsg[] = {<br />
　　　　/* 0 */　　　 "No error",　　　　　　　　<br />
　　　　/* 1 */　　　 "Open file error",　　　　<br />
　　　　/* 2 */　　　 "Failed in sending/receiving a message",　<br />
　　　　/* 3 */　　　 "Bad arguments",　<br />
　　　　/* 4 */　　　 "Memeroy is not enough",<br />
　　　　/* 5 */　　　 "Service is down; try later",<br />
　　　　/* 6 */　　　 "Unknow information",<br />
　　　　/* 7 */　　　 "A socket operation has failed",<br />
　　　　/* 8 */　　　 "Permission denied",<br />
　　　　/* 9 */　　　 "Bad configuration file format",　<br />
　　　　/* 10 */　　　"Communication time out",<br />
　　};<br />
　　　　　　　　　　　　　　　<br />
　　/* 声明错误代码全局变量 */<br />
　　long errno = 0;<br />
　　<br />
　　/* 打印出错信息函数 */<br />
　　void perror( char* info)<br />
　　{<br />
　　　　if ( info ){<br />
　　　　　　printf("%s: %sn", info, errmsg[errno] );<br />
　　　　　　return;<br />
　　　　}<br />
　　　　<br />
　　　　printf("Error: %sn", errmsg[errno] );<br />
　　}<br />
这个基本上是ANSI的错误处理实现细节了，于是当你程序中有错误时你就可以这样处理：<br />
　　bool CheckPermission( char* userName )<br />
　　{<br />
　　　　if ( strcpy(userName, "root") != 0 ){<br />
　　　　　　errno = ERR_PERMISSION_DENIED;<br />
　　　　　　return (FALSE);<br />
　　　　}<br />
　　　　<br />
　　　　...<br />
　　}<br />
　　<br />
　　main()<br />
　　{<br />
　　　　...<br />
　　　　if (! CheckPermission( username ) ){<br />
　　　　　　perror("main()");<br />
　　　　}<br />
　　　　...<br />
　　}<br />
　　　　　　　　　　　　　　　<br />
一个即有共性，也有个性的错误信息处理，这样做有利同种错误出一样的信息，统一用户界面，而不会因为文件打开失败，A程序员出一个信息，B程序员又出一个信息。而且这样做，非常容易维护。代码也易读。<br />
当然，物极必反，也没有必要把所有的输出都放到errmsg中，抽取比较重要的出错信息或是提示信息是其关键，但即使这样，这也包括了大多数的信息。</p>
<p>12、常用函数和循环语句中的被计算量<br />
-----------------<br />
看一下下面这个例子：<br />
　　for( i=0; i<1000; i++ ){<br />
　　　　GetLocalHostName( hostname );<br />
　　　　...<br />
　　}<br />
　　<br />
GetLocalHostName的意思是取得当前计算机名，在循环体中，它会被调用1000次啊。这是多么的没有效率的事啊。应该把这个函数拿到循环体外，这样只调用一次，效率得到了很大的提高。虽然，我们的编译器会进行优化，会把循环体内的不变的东西拿到循环外面，但是，你相信所有编译器会知道哪些是不变的吗？我觉得编译器不可靠。最好还是自己动手吧。<br />
同样，对于常用函数中的不变量，如：<br />
GetLocalHostName(char* name)<br />
{<br />
　　char funcName[] = "GetLocalHostName";<br />
　　<br />
　　sys_log( "%s begin......", funcName );<br />
　　...<br />
　　sys_log( "%s end......", funcName );<br />
}<br />
如果这是一个经常调用的函数，每次调用时都要对funcName进行分配内存，这个开销很大啊。把这个变量声明成static吧，当函数再次被调用时，就会省去了分配内存的开销，执行效率也很好。<br />
　　</p>
<p>13、函数名和变量名的命名<br />
------------<br />
我看到许多程序对变量名和函数名的取名很草率，特别是变量名，什么a,b,c,aa,bb,cc，还有什么flag1,flag2, cnt1, cnt2，这同样是一种没有“修养”的行为。即便加上好的注释。好的变量名或是函数名，我认为应该有以下的规则：<br />
　　<br />
　　1) 直观并且可以拼读，可望文知意，不必“解码”。<br />
　　2) 名字的长度应该即要最短的长度，也要能最大限度的表达其含义。<br />
　　3) 不要全部大写，也不要全部小写，应该大小写都有，如：GetLocalHostName 或是 UserAccount。<br />
　　4) 可以简写，但简写得要让人明白，如：ErrorCode -> ErrCode,　ServerListener -> ServLisner，UserAccount -> UsrAcct 等。<br />
　　5) 为了避免全局函数和变量名字冲突，可以加上一些前缀，一般以模块简称做为前缀。<br />
　　6) 全局变量统一加一个前缀或是后缀，让人一看到这个变量就知道是全局的。<br />
　　7) 用匈牙利命名法命名函数参数，局部变量。但还是要坚持“望文生意”的原则。<br />
　　8) 与标准库（如：STL）或开发库（如：MFC）的命名风格保持一致。<br />
　　</p>
<p>　　<br />
14、函数的传值和传指针<br />
&#8212;&#8212;&#8212;&#8212;<br />
向函数传参数时，一般而言，传入非const的指针时，就表示，在函数中要修改这个指针把指内存中的数据。如果是传值，那么无论在函数内部怎么修改这个值，也影响不到传过来的值，因为传值是只内存拷贝。<br />
什么？你说这个特性你明白了，好吧，让我们看看下面的这个例程：<br />
void<br />
GetVersion(char* pStr)<br />
{<br />
　　pStr = malloc(10);<br />
　　strcpy ( pStr, &#8220;2.0&#8243; );<br />
}<br />
main()<br />
{<br />
　　char* ver = NULL;<br />
　　GetVersion ( ver );<br />
　　&#8230;<br />
　　&#8230;<br />
　　free ( ver );<br />
}<br />
我保证，类似这样的问题是一个新手最容易犯的错误。程序中妄图通过函数GetVersion给指针ver分配空间，但这种方法根本没有什么作用，原因就是&#8211;这是传值，不是传指针。你或许会和我争论，我分明传的时指针啊？再仔细看看，其实，你传的是指针其实是在传值。</p>
<p>15、修改别人程序的修养<br />
&#8212;&#8212;&#8212;&#8211;<br />
当你维护别人的程序时，请不要非常主观臆断的把已有的程序删除或是修改。我经常看到有的程序员直接在别人的程序上修改表达式或是语句。修改别人的程序时，请不要删除别人的程序，如果你觉得别人的程序有所不妥，请注释掉，然后添加自己的处理程序，必竟，你不可能100%的知道别人的意图，所以为了可以恢复，请不依赖于CVS或是SourceSafe这种版本控制软件，还是要在源码上给别人看到你修改程序的意图和步骤。这是程序维护时，一个有修养的程序员所应该做的。<br />
如下所示，这就是一种比较好的修改方法：<br />
　　/*<br />
　　 * &#8212;&#8211; commented by haoel 2003/04/12 &#8212;&#8212;<br />
　　 *<br />
　　 *　 char* p = ( char* ) malloc( 10 );<br />
　　 *　 memset( p, 0, 10 );<br />
　　 */<br />
　　<br />
　　/* &#8212;&#8212; Added by haoel　 2003/04/12 &#8212;&#8211; */<br />
　　 char* p = ( char* )calloc( 10, sizeof char );<br />
　　/* &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;- */<br />
　　&#8230;<br />
当然，这种方法是在软件维护时使用的，这样的方法，可以让再维护的人很容易知道以前的代码更改的动作和意图，而且这也是对原作者的一种尊敬。<br />
以“注释 &#8211; 添加”方式修改别人的程序，要好于直接删除别人的程序。<br />
16、把相同或近乎相同的代码形成函数和宏<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
有人说，最好的程序员，就是最喜欢“偷懒”的程序，其中不无道理。<br />
如果你有一些程序的代码片段很相似，或直接就是一样的，请把他们放在一个函数中。而如果这段代码不多，而且会被经常使用，你还想避免函数调用的开销，那么就把他写成宏吧。<br />
千万不要让同一份代码或是功能相似的代码在多个地方存在，不然如果功能一变，你就要修改好几处地方，这种会给维护带来巨大的麻烦，所以，做到“一改百改”，还是要形成函数或是宏。</p>
<p>17、表达式中的括号<br />
&#8212;&#8212;&#8212;<br />
如果一个比较复杂的表达式中，你并不是很清楚各个操作符的忧先级，即使是你很清楚优先级，也请加上括号，不然，别人或是自己下一次读程序时，一不小心就看走眼理解错了，为了避免这种“误解”，还有让自己的程序更为清淅，还是加上括号吧。<br />
比如，对一个结构的成员取地址：<br />
　　GetUserAge( &#038;( UserInfo->age ) );<br />
虽然，&#038;UserInfo->age中，->操作符的优先级最高，但加上一个括号，会让人一眼就看明白你的代码是什么意思。<br />
再比如，一个很长的条件判断：<br />
if ( ( ch[0] >= &acute;0&acute; || ch[0] <= &acute;9&acute; ) &#038;&#038;<br />
　　 ( ch[1] >= &acute;a&acute; || ch[1] <= &acute;z&acute; ) &#038;&#038;<br />
　　 ( ch[2] >= &acute;A&acute; || ch[2] <= &acute;Z&acute; )　　)<br />
　　<br />
括号，再加上空格和换行，你的代码是不是很容易读懂了？　　</p>
<p>18、函数参数中的const<br />
-----------<br />
对于一些函数中的指针参数，如果在函数中只读，请将其用const修饰，这样，别人一读到你的函数接口时，就会知道你的意图是这个参数是[in]，如果没有const时，参数表示[in/out]，注意函数接口中的const使用，利于程序的维护和避免犯一些错误。<br />
虽然，const修饰的指针，如：const char* p，在C中一点用也没有，因为不管你的声明是不是const，指针的内容照样能改，因为编译器会强制转换，但是加上这样一个说明，有利于程序的阅读和编译。因为在C中，修改一个const指针所指向的内存时，会报一个Warning。这会引起程序员的注意。<br />
C++中对const定义的就很严格了，所以C++中要多多的使用const，const的成员函数，const的变量，这样会对让你的代码和你的程序更加完整和易读。（关于C++的const我就不多说了）</p>
<p>19、函数的参数个数（多了请用结构）<br />
-----------------<br />
函数的参数个数最好不要太多，一般来说6个左右就可以了，众多的函数参数会让读代码的人一眼看上去就很头昏，而且也不利于维护。如果参数众多，还请使用结构来传递参数。这样做有利于数据的封装和程序的简洁性。<br />
也利于使用函数的人，因为如果你的函数个数很多，比如12个，调用者很容易搞错参数的顺序和个数，而使用结构struct来传递参数，就可以不管参数的顺序。<br />
而且，函数很容易被修改，如果需要给函数增加参数，不需要更改函数接口，只需更改结构体和函数内部处理，而对于调用函数的程序来说，这个动作是透明的。</p>
<p>20、函数的返回类型，不要省略<br />
--------------<br />
我看到很多程序写函数时，在函数的返回类型方面不太注意。如果一个函数没有返回值，也请在函数前面加上void的修饰。而有的程序员偷懒，在返回int的函数则什么不修饰（因为如果不修饰，则默认返回int），这种习惯很不好，还是为了原代码的易读性，加上int吧。<br />
所以函数的返回值类型，请不要省略。<br />
另外，对于void的函数，我们往往会忘了return，由于某些C/C++的编译器比较敏感，会报一些警告，所以即使是void的函数，我们在内部最好也要加上return的语句，这有助于代码的编译。</p>
<p>21、goto语句的使用<br />
---------<br />
N年前，软件开发的一代宗师--迪杰斯特拉(Dijkstra)说过：“goto statment is harmful !!”，并建议取消goto语句。因为goto语句不利于程序代码的维护性。<br />
这里我也强烈建议不要使用goto语句，除非下面的这种情况：</p>
<p>　　#define FREE(p) if(p) {<br />
　　　　　　　　　　　　free(p);<br />
　　　　　　　　　　　　p = NULL;<br />
　　　　　　　　　　}<br />
　　main()<br />
　　{<br />
　　　　char *fname=NULL, *lname=NULL, *mname=NULL;<br />
　　　　fname = ( char* ) calloc ( 20, sizeof(char) );<br />
　　　　if ( fname == NULL ){<br />
　　　　　　goto ErrHandle;<br />
　　　　}<br />
　　　　lname = ( char* ) calloc ( 20, sizeof(char) );<br />
　　　　if ( lname == NULL ){<br />
　　　　　　goto ErrHandle;<br />
　　　　}<br />
　　　　mname = ( char* ) calloc ( 20, sizeof(char) );<br />
　　　　if ( mname == NULL ){<br />
　　　　　　goto ErrHandle;<br />
　　　　}<br />
　　　　<br />
　　　　......<br />
　　<br />
　　　　<br />
　　 ErrHandle:<br />
　　　　FREE(fname);<br />
　　　　FREE(lname);<br />
　　　　FREE(mname);<br />
　　　　ReportError(ERR_NO_MEMOEY);<br />
　　 }<br />
也只有在这种情况下，goto语句会让你的程序更易读，更容易维护。（在用嵌C来对数据库设置游标操作时，或是对数据库建立链接时，也会遇到这种结构）</p>
<p>22、宏的使用<br />
------<br />
很多程序员不知道C中的“宏”到底是什么意思？特别是当宏有参数的时候，经常把宏和函数混淆。我想在这里我还是先讲讲“宏”，宏只是一种定义，他定义了一个语句块，当程序编译时，编译器首先要执行一个“替换”源程序的动作，把宏引用的地方替换成宏定义的语句块，就像文本文件替换一样。这个动作术语叫“宏的展开”<br />
使用宏是比较“危险”的，因为你不知道宏展开后会是什么一个样子。例如下面这个宏：<br />
　　#define　MAX(a, b)　　 a>b?a:b<br />
当我们这样使用宏时，没有什么问题： MAX( num1, num2 ); 因为宏展开后变成 num1>num2?num1:num2；。但是，如果是这样调用的，MAX( 17+32, 25+21 ); 呢，编译时出现错误，原因是，宏展开后变成：17+32>25+21?17+32:25+21，哇，这是什么啊？<br />
所以，宏在使用时，参数一定要加上括号，上述的那个例子改成如下所示就能解决问题了。<br />
　　#define　MAX( (a), (b) )　　 (a)>(b)?(a):(b)<br />
　　<br />
即使是这样，也不这个宏也还是有Bug，因为如果我这样调用 MAX(i++, j++); ，经过这个宏以后，i和j都被累加了两次，这绝不是我们想要的。<br />
　　<br />
所以，在宏的使用上还是要谨慎考虑，因为宏展开是的结果是很难让人预料的。而且虽然，宏的执行很快（因为没有函数调用的开销），但宏会让源代码澎涨，使目标文件尺寸变大，（如：一个50行的宏，程序中有1000个地方用到，宏展开后会很不得了），相反不能让程序执行得更快（因为执行文件变大，运行时系统换页频繁）。<br />
因此，在决定是用函数，还是用宏时得要小心。</p>
<p>23、static的使用<br />
&#8212;&#8212;&#8211;<br />
static关键字，表示了“静态”，一般来说，他会被经常用于变量和函数。一个static的变量，其实就是全局变量，只不过他是有作用域的全局变量。比如一个函数中的static变量：<br />
char*<br />
getConsumerName()<br />
{<br />
　　static int cnt = 0;<br />
　　<br />
　　&#8230;.<br />
　　cnt++;<br />
　　&#8230;.<br />
}<br />
cnt变量的值会跟随着函数的调用次而递增，函数退出后，cnt的值还存在，只是cnt只能在函数中才能被访问。而cnt的内存也只会在函数第一次被调用时才会被分配和初始化，以后每次进入函数，都不为static分配了，而直接使用上一次的值。<br />
对于一些被经常调用的函数内的常量，最好也声明成static（参见第12条）<br />
但static的最多的用处却不在这里，其最大的作用的控制访问，在C中如果一个函数或是一个全局变量被声明为static，那么，这个函数和这个全局变量，将只能在这个C文件中被访问，如果别的C文件中调用这个C文件中的函数，或是使用其中的全局（用extern关键字），将会发生链接时错误。这个特性可以用于数据和程序保密。</p>
<p>24、函数中的代码尺寸<br />
&#8212;&#8212;&#8212;-<br />
一个函数完成一个具体的功能，一般来说，一个函数中的代码最好不要超过600行左右，越少越好，最好的函数一般在100行以内，300行左右的孙函数就差不多了。有证据表明，一个函数中的代码如果超过500行，就会有和别的函数相同或是相近的代码，也就是说，就可以再写另一个函数。<br />
另外，函数一般是完成一个特定的功能，千万忌讳在一个函数中做许多件不同的事。函数的功能越单一越好，一方面有利于函数的易读性，另一方面更有利于代码的维护和重用，功能越单一表示这个函数就越可能给更多的程序提供服务，也就是说共性就越多。<br />
虽然函数的调用会有一定的开销，但比起软件后期维护来说，增加一些运行时的开销而换来更好的可维护性和代码重用性，是很值得的一件事。</p>
<p>25、typedef的使用<br />
&#8212;&#8212;&#8212;<br />
typedef是一个给类型起别名的关键字。不要小看了它，它对于你代码的维护会有很好的作用。比如C中没有bool，于是在一个软件中，一些程序员使用int，一些程序员使用short，会比较混乱，最好就是用一个typedef来定义，如：<br />
　　typedef char bool;<br />
　　<br />
一般来说，一个C的工程中一定要做一些这方面的工作，因为你会涉及到跨平台，不同的平台会有不同的字长，所以利用预编译和typedef可以让你最有效的维护你的代码，如下所示：<br />
　　#ifdef SOLARIS2_5<br />
　　　typedef boolean_t　　 BOOL_T;<br />
　　#else<br />
　　　typedef int　　　　　 BOOL_T;<br />
　　#endif<br />
　　<br />
　　typedef short　　　　　 INT16_T;<br />
　　typedef unsigned short　UINT16_T;<br />
　　typedef int　　　　　　 INT32_T;<br />
　　typedef unsigned int　　UINT32_T;<br />
　　<br />
　　#ifdef WIN32<br />
　　　typedef _int64　　　　INT64_T;<br />
　　#else<br />
　　　typedef long long　　 INT64_T;<br />
　　#endif<br />
　　<br />
　　typedef float　　　　　 FLOAT32_T;<br />
　　typedef char*　　　　　 STRING_T;<br />
　　typedef unsigned char　 BYTE_T;<br />
　　typedef time_t　　　　　TIME_T;<br />
　　typedef INT32_T　　　　 PID_T;<br />
　　<br />
使用typedef的其它规范是，在结构和函数指针时，也最好用typedef，这也有利于程序的易读和可维护性。如：<br />
　　typedef struct _hostinfo {<br />
　　　　HOSTID_T　 host;<br />
　　　　INT32_T　　hostId;<br />
　　　　STRING_T　 hostType;<br />
　　　　STRING_T　 hostModel;<br />
　　　　FLOAT32_T　cpuFactor;<br />
　　　　INT32_T　　numCPUs;<br />
　　　　INT32_T　　nDisks;<br />
　　　　INT32_T　　memory;<br />
　　　　INT32_T　　swap;<br />
　　} HostInfo;</p>
<p>　　typedef INT32_T (*RsrcReqHandler)(<br />
　　 void *info,<br />
　　 JobArray *jobs,<br />
　　 AllocInfo *allocInfo,<br />
　　 AllocList *allocList);<br />
C++中这样也是很让人易读的：<br />
　　typedef CArray<HostInfo, HostInfo&#038;> HostInfoArray;<br />
于是，当我们用其定义变量时，会显得十分易读。如：<br />
　　HostInfo* phinfo;<br />
　　RsrcReqHandler* pRsrcHand;<br />
这种方式的易读性，在函数的参数中十分明显。<br />
关键是在程序种使用typedef后，几乎所有的程序中的类型声明都显得那么简洁和清淅，而且易于维护，这才是typedef的关键。</p>
<p>26、为常量声明宏<br />
&#8212;&#8212;&#8211;<br />
最好不要在程序中出现数字式的“硬编码”，如：<br />
　　int user[120];<br />
　　<br />
为这个120声明一个宏吧。为所有出现在程序中的这样的常量都声明一个宏吧。比如TimeOut的时间，最大的用户数量，还有其它，只要是常量就应该声明成宏。如果，突然在程序中出现下面一段代码，<br />
　　for ( i=0; i<120; i++){<br />
　　　　....<br />
　　}<br />
120是什么？为什么会是120？这种“硬编码”不仅让程序很读，而且也让程序很不好维护，如果要改变这个数字，得同时对所有程序中这个120都要做修改，这对修改程序的人来说是一个很大的痛苦。所以还是把常量声明成宏，这样，一改百改，而且也很利于程序阅读。<br />
　　#define MAX_USR_CNT 120<br />
　　<br />
　　for ( i=0; i<MAX_USER_CNT; i++){<br />
　　　　....<br />
　　}<br />
这样就很容易了解这段程序的意图了。<br />
有的程序员喜欢为这种变量声明全局变量，其实，全局变量应该尽量的少用，全局变量不利于封装，也不利于维护，而且对程序执行空间有一定的开销，一不小心就造成系统换页，造成程序执行速度效率等问题。所以声明成宏，即可以免去全局变量的开销，也会有速度上的优势。</p>
<p>27、不要为宏定义加分号<br />
-----------<br />
有许多程序员不知道在宏定义时是否要加分号，有时，他们以为宏是一条语句，应该要加分号，这就错了。当你知道了宏的原理，你会赞同我为会么不要为宏定义加分号的。看一个例子：<br />
　　#define MAXNUM 1024;<br />
这是一个有分号的宏，如果我们这样使用：<br />
　　half = MAXNUM/2;<br />
　　<br />
　　if ( num < MAXNUM )<br />
等等，都会造成程序的编译错误，因为，当宏展开后，他会是这个样子的：<br />
　　half = 1024;/2;<br />
　　<br />
　　if ( num < 1024; )<br />
　　<br />
是的，分号也被展进去了，所以造成了程序的错误。请相信我，有时候，一个分号会让你的程序出现成百个错误。所以还是不要为宏加最后一个分号，哪怕是这样：<br />
　　#define LINE　　"================================="<br />
　　<br />
　　#define PRINT_LINE　printf(LINE)<br />
　　#define PRINT_NLINE(n)　while ( n-- >0 ) { PRINT_LINE; }<br />
　　<br />
都不要在最后加上分号，当我们在程序中使用时，为之加上分号，<br />
　　main()<br />
　　{<br />
　　　　char *p = LINE;<br />
　　　　PRINT_LINE;<br />
　　}<br />
这一点非常符合习惯，而且，如果忘加了分号，编译器给出的错误提示，也会让我们很容易看懂的。<br />
28、||和&#038;&#038;的语句执行顺序<br />
&#8212;&#8212;&#8212;&#8212;<br />
条件语句中的这两个“与”和“或”操作符一定要小心，它们的表现可能和你想像的不一样，这里条件语句中的有些行为需要和说一下：<br />
　　express1 || express2<br />
　　　　<br />
　　先执行表达式express1如果为“真”，express2将不被执行，express2仅在express1为“假”时才被执行。因为第一个表达式为真了，整个表达式都为真，所以没有必要再去执行第二个表达式了。<br />
　　express1 &#038;&#038; express2<br />
　　先执行表达式express1如果为“假”，express2将不被执行，express2仅在express1为“真”时才被执行。因为第一个表达式为假了，整个表达式都为假了，所以没有必要再去执行第二个表达式了。</p>
<p>于是，他并不是你所想像的所有的表达式都会去执行，这点一定要明白，不然你的程序会出现一些莫明的运行时错误。<br />
例如，下面的程序：</p>
<p>　　if ( sum > 100 &#038;&#038;<br />
　　　　 ( ( fp=fopen( filename,&#8221;a&#8221; ) ) != NULL )　 {<br />
　　　　<br />
　　　　 fprintf(fp, &#8220;Warring: it beyond one hundredn&#8221;);<br />
　　　　 &#8230;&#8230;<br />
　　}<br />
　　<br />
　　fprintf( fp, &#8221; sum is %id n&#8221;, sum );<br />
　　fclose( fp );<br />
本来的意图是，如果sum > 100 ，向文件中写一条出错信息，为了方便，把两个条件判断写在一起，于是，如果sum<=100时，打开文件的操作将不会做，最后，fprintf和fclose就会发现未知的结果。<br />
再比如，如果我想判断一个字符是不是有内容，我得判断这个字符串指针是不为空（NULL）并且其内容不能为空（Empty），一个是空指针，一个是空内容。我也许会这样写：<br />
　　if ( ( p != NULL ) &#038;&#038; ( strlen(p) != 0 ))<br />
于是，如果p为NULL，那么strlen(p)就不会被执行，于是，strlen也就不会因为一个空指针而“非法操作”或是一个“Core Dump”了。<br />
记住一点，条件语句中，并非所有的语句都会执行，当你的条件语句非常多时，这点要尤其注意。</p>
<p>29、尽量用for而不是while做循环<br />
---------------<br />
基本上来说，for可以完成while的功能，我是建议尽量使用for语句，而不要使用while语句，特别是当循环体很大时，for的优点一下就体现出来了。<br />
因为在for中，循环的初始、结束条件、循环的推进，都在一起，一眼看上去就知道这是一个什么样的循环。刚出学校的程序一般对于链接喜欢这样来：<br />
　　p = pHead;<br />
　　<br />
　　while ( p ){<br />
　　　　...<br />
　　　　...<br />
　　　　p = p->next;<br />
　　}<br />
当while的语句块变大后，你的程序将很难读，用for就好得多：<br />
　　for ( p=pHead;　p; p=p->next ){<br />
　　..<br />
　　}<br />
一眼就知道这个循环的开始条件，结束条件，和循环的推进。大约就能明白这个循环要做个什么事？而且，程序维护进来很容易，不必像while一样，在一个编辑器中上上下下的捣腾。</p>
<p>30、请sizeof类型而不是变量<br />
&#8212;&#8212;&#8212;&#8212;-<br />
许多程序员在使用sizeof中，喜欢sizeof变量名，例如：<br />
int score[100];<br />
char filename[20];<br />
struct UserInfo usr[100];<br />
在sizeof这三个的变量名时，都会返回正确的结果，于是许多程序员就开始sizeof变量名。这个习惯很虽然没有什么不好，但我还是建议sizeof类型。<br />
我看到过这个的程序：<br />
　　pScore = (int*) malloc( SUBJECT_CNT );<br />
　　memset( pScore, 0, sizeof(pScore) );<br />
　　&#8230;<br />
　　<br />
此时，sizeof(pScore)返回的就是4（指针的长度），不会是整个数组，于是，memset就不能对这块内存进行初始化。为了程序的易读和易维护，我强烈建议使用类型而不是变量，如：<br />
对于score：　　 sizeof(int) * 100　 /* 100个int */<br />
对于filename：　sizeof(char) * 20　 /* 20个char */<br />
对于usr：　　　 sizeof(struct UserInfo) * 100　 /* 100个UserInfo */<br />
这样的代码是不是很易读？一眼看上去就知道什么意思了。</p>
<p>另外一点，sizeof一般用于分配内存，这个特性特别在多维数组时，就能体现出其优点了。如，给一个字符串数组分配内存，<br />
/*<br />
* 分配一个有20个字符串，<br />
* 每个字符串长100的内存<br />
*/<br />
char* *p;<br />
/*<br />
* 错误的分配方法<br />
*/<br />
p = (char**)calloc( 20*100, sizeof(char) );</p>
<p>/*<br />
* 正确的分配方法<br />
*/<br />
p = (char**) calloc ( 20, sizeof(char*) );<br />
for ( i=0; i<20; i++){<br />
　　/*p = (char*) calloc ( 100, sizeof(char) );*/<br />
　　p = (char*) calloc ( 100, sizeof(char) );<br />
}<br />
（注：上述语句被注释掉的是原来的，是错误的，由dasherest朋友指正，谢谢）<br />
为了代码的易读，省去了一些判断，请注意这两种分配的方法，有本质上的差别。</p>
<p>31、不要忽略Warning<br />
----------<br />
对于一些编译时的警告信息，请不要忽视它们。虽然，这些Warning不会妨碍目标代码的生成，但这并不意味着你的程序就是好的。必竟，并不是编译成功的程序才是正确的，编译成功只是万里长征的第一步，后面还有大风大浪在等着你。从编译程序开始，不但要改正每个error，还要修正每个warning。这是一个有修养的程序员该做的事。<br />
一般来说，一面的一些警告信息是常见的：<br />
　　1）声明了未使用的变量。（虽然编译器不会编译这种变量，但还是把它从源程序中注释或是删除吧）<br />
　　2）使用了隐晦声明的函数。（也许这个函数在别的C文件中，编译时会出现这种警告，你应该这使用之前使用extern关键字声明这个函数）<br />
　　3）没有转换一个指针。（例如malloc返回的指针是void的，你没有把之转成你实际类型而报警，还是手动的在之前明显的转换一下吧）<br />
　　4）类型向下转换。（例如：float f = 2.0; 这种语句是会报警告的，编译会告诉你正试图把一个double转成float，你正在阉割一个变量，你真的要这样做吗？还是在2.0后面加个f吧，不然，2.0就是一个double，而不是float了）<br />
　　<br />
不管怎么说，编译器的Warning不要小视，最好不要忽略，一个程序都做得出来，何况几个小小的Warning呢？</p>
<p>32、书写Debug版和Release版的程序<br />
----------------<br />
程序在开发过程中必然有许多程序员加的调试信息。我见过许多项目组，当程序开发结束时，发动群众删除程序中的调试信息，何必呢？为什么不像VC++那样建立两个版本的目标代码？一个是debug版本的，一个是Release版的。那些调试信息是那么的宝贵，在日后的维护过程中也是很宝贵的东西，怎么能说删除就删除呢？<br />
利用预编译技术吧，如下所示声明调试函数：<br />
　　#ifdef DEBUG<br />
　　　　void TRACE(char* fmt, ...)<br />
　　　　{<br />
　　　　　　......<br />
　　　　}<br />
　　#else<br />
　　　　#define TRACE(char* fmt, ...)<br />
　　#endif<br />
于是，让所有的程序都用TRACE输出调试信息，只需要在在编译时加上一个参数“-DDEBUG”，如：<br />
　　cc -DDEBUG -o target target.c<br />
于是，预编译器发现DEBUG变量被定义了，就会使用TRACE函数。而如果要发布给用户了，那么只需要把取消“-DDEBUG”的参数，于是所有用到 TRACE宏，这个宏什么都没有，所以源程序中的所有TRACE语言全部被替换成了空。一举两得，一箭双雕，何乐而不为呢？<br />
顺便提一下，两个很有用的系统宏，一个是“__FILE__”，一个是“__LINE__”，分别表示，所在的源文件和行号，当你调试信息或是输出错误时，可以使用这两个宏，让你一眼就能看出你的错误，出现在哪个文件的第几行中。这对于用C/C++做的大工程非常的管用。</p>
<p>综上所述32条，都是为了三大目的--<br />
　　1、程序代码的易读性。<br />
　　2、程序代码的可维护性，<br />
　　3、程序代码的稳定可靠性。<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
　　<br />
有修养的程序员，就应该要学会写出这样的代码！这是任何一个想做编程高手所必需面对的细小的问题，编程高手不仅技术要强，基础要好，而且最重要的是要有“修养”！<br />
好的软件产品绝不仅仅是技术，而更多的是整个软件的易维护和可靠性。　　<br />
软件的维护有大量的工作量花在代码的维护上，软件的Upgrade，也有大量的工作花在代码的组织上，所以好的代码，清淅的，易读的代码，将给大大减少软件的维护和升级成本。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.evanjiang.net.cn/archives/842.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>高效开发与彻底测试</title>
		<link>http://www.evanjiang.net.cn/archives/712.html</link>
		<comments>http://www.evanjiang.net.cn/archives/712.html#comments</comments>
		<pubDate>Thu, 05 Mar 2009 06:14:35 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[vc++/vc#]]></category>
		<category><![CDATA[技术感悟]]></category>
		<category><![CDATA[系统架构]]></category>
		<category><![CDATA[高效开发与彻底测试]]></category>

		<guid isPermaLink="false">http://www.evanjiang.net.cn/?p=712</guid>
		<description><![CDATA[<p style="float: right;margin: 4px;">


</p> <p>一、“千般路”与“磨豆腐”</p>
<p>    很久以前听一个故事：从前有个小伙子，少时有大志，长大后却无好营生，开了个豆腐作坊，每天磨豆腐累得腰酸背疼。每到夜深人静，小伙子辗转反侧，总想找条更好的“事业之路”，可是想过千百条、尝试过几十条路，都走不通。夜不成寝，白天干活更累，小伙子不由慨叹：“晚上想过千般路，白天还得磨豆腐”。</p>
<p>    不久以前看过一篇文章：《CMM欺骗了中国的软件业》，内容是对CMM热的反思。CMM当然不会主动欺骗人民，实际上是我们的软件业自己欺骗自己。我们从来不缺少“某某模式”，“面向某某”，“某某认证”等等听起来美妙无比的东西，问题是实际的研发过程中能做得到码？现实是残酷的，美妙的概念漫天飞舞，开发过程仍然是作坊式的，正是：“晚上想过千般路，白天还得磨豆腐”。</p>
<p>    中国的故事通常都有圆满的结局，现在接着说“磨豆腐”的故事。过了很长时间，小伙子终于面对现实，不再沉迷于不切实际的空想，用心磨好豆腐，闲时琢磨些个窍门，慢慢地，他的豆腐质量越来越好，每天产量也越来越多，作坊越开越大，成了远近闻名的“豆腐老板”，后来，他做起了别的生意，发现年轻时的空想，其实很多都是可行的，因为现在“能力”和“财力”都不同。</p>
<p>    再说软件开发。我们不反对任何理论、技术、方法、模式等等，但第一，您的企业或团队做得到吗？不要做“如果开发时间延长一倍，就可以做到”之类毫无意义的假设。第二，做了真的有效益吗？效益是指扣除成本之后的收益。如果不具备这两点，那么还是不要整天想着“千般路”，首先想想如何好好的“磨豆腐”吧。</p>
<p>    对于所有软件开发来说，代码编写都是无可逃避的“磨豆腐”。改进代码编写工作，高率效低成本地开发出高质量的代码，对于软件产品能否在激烈的竞争中胜出，对于软件企业的生存和发展，都具有重要的现实意义。

    本文是Visual Unit应用的范例项目C++代码文档生成器的主题文档，叙述的正是改进代码编写工作的方法和工具，所有内容均经过实战检验，具有&#8221;可行&#8221;和&#8221;效益&#8221;两个特征，&#8221;可行&#8221;是指较低门槛或没有门槛，凭现有条件即可实施；&#8221;效益&#8221;是指能产生立竿见影的效果。</p>
<p>    本文所援引的范例项目，模拟最糟糕的开发团队，最混乱的开发流程：由很少写代码的测试和预研部门开发，人员不固定，时间也不固定，谁有空就写上一些；没有设计，没有文档，基至也不在代码文件中保存编码人员的信息，成员完全依赖于阅读代码和测试用例来理解其他成员写的代码；除了简单的命名规则外，没有其他规范，甚至连一个函数原则上不能超过50行之类的基本规范也没有（范例中有超过200行的函数CMacro::Unwind()，一万多条路径）。 任何开发团队和开发流程都会好于范例项目的开发团队和开发流程，因此，范例所展示的方法和工具，具有&#8221;广泛可行性&#8221;。</p>
<p>    本文介绍如何进行高效编码调试和实现彻底的单元测试。编码调试是任何软件开发都无可逃避的工作，在Visual Unit的支持下编码调试，只是把本来就一定要做的工作改变一下方式，不需要多做什么，就可以大幅提高编程效率和质量；另一方面，Visual Unit彻底改变了单元测试难于实施或成本昂贵的局面，无论团队中开发与测试人员的比例是怎么样的，都可以轻松快捷地实现彻底的单元测试。 </p>
<p>　
二、高效编码调试</p>
<p>    任何软件开发，都离不开编码调试。对于稍为复杂一点的函数，一般来说，编写几行代码，就要执行一下，看它们是否按预想的工作，然后再继续写，写完后还要将各种可能输入都执行一下。如何执行？一般由别的代码来调用，也就是说需要驱动，驱动通常是在开始编写函数实现代码之前建立，这样才能一边编写一边调试。驱动大致可分为自然驱动和专门驱动。</p>
<p>    自然驱动：利用项目中已有的代码作为驱动，通常是在被调试的函数中加断点，从界面执行一个需要调用该函数的功能，调试器中断时就可以调试了；专门驱动：为需要调试的函数编写专门的驱动代码，通过执行驱动代码来执行被调试函数。</p>
<p>    自然驱动的主要优点是不需要其他工作就可以直接调试，甚至感觉不到需要驱动，主要缺点是输入数据通常是公共的，即很多代码都使用相同的输入源进行调试，实际输入往往是经过其他代码处理后的中间结果，要针对各种可能输入都进行调试往往很困难，造成调试不全面，程序员的思维受到局限，难于做到全面地考虑各种可能输入。</p>
<p>  [...]]]></description>
			<content:encoded><![CDATA[<p>一、“千般路”与“磨豆腐”</p>
<p>    很久以前听一个故事：从前有个小伙子，少时有大志，长大后却无好营生，开了个豆腐作坊，每天磨豆腐累得腰酸背疼。每到夜深人静，小伙子辗转反侧，总想找条更好的“事业之路”，可是想过千百条、尝试过几十条路，都走不通。夜不成寝，白天干活更累，小伙子不由慨叹：“晚上想过千般路，白天还得磨豆腐”。</p>
<p>    不久以前看过一篇文章：《CMM欺骗了中国的软件业》，内容是对CMM热的反思。CMM当然不会主动欺骗人民，实际上是我们的软件业自己欺骗自己。我们从来不缺少“某某模式”，“面向某某”，“某某认证”等等听起来美妙无比的东西，问题是实际的研发过程中能做得到码？现实是残酷的，美妙的概念漫天飞舞，开发过程仍然是作坊式的，正是：“晚上想过千般路，白天还得磨豆腐”。</p>
<p>    中国的故事通常都有圆满的结局，现在接着说“磨豆腐”的故事。过了很长时间，小伙子终于面对现实，不再沉迷于不切实际的空想，用心磨好豆腐，闲时琢磨些个窍门，慢慢地，他的豆腐质量越来越好，每天产量也越来越多，作坊越开越大，成了远近闻名的“豆腐老板”，后来，他做起了别的生意，发现年轻时的空想，其实很多都是可行的，因为现在“能力”和“财力”都不同。</p>
<p>    再说软件开发。我们不反对任何理论、技术、方法、模式等等，但第一，您的企业或团队做得到吗？不要做“如果开发时间延长一倍，就可以做到”之类毫无意义的假设。第二，做了真的有效益吗？效益是指扣除成本之后的收益。如果不具备这两点，那么还是不要整天想着“千般路”，首先想想如何好好的“磨豆腐”吧。</p>
<p>    对于所有软件开发来说，代码编写都是无可逃避的“磨豆腐”。改进代码编写工作，高率效低成本地开发出高质量的代码，对于软件产品能否在激烈的竞争中胜出，对于软件企业的生存和发展，都具有重要的现实意义。<br />
<span id="more-712"></span><br />
    本文是Visual Unit应用的范例项目C++代码文档生成器的主题文档，叙述的正是改进代码编写工作的方法和工具，所有内容均经过实战检验，具有&#8221;可行&#8221;和&#8221;效益&#8221;两个特征，&#8221;可行&#8221;是指较低门槛或没有门槛，凭现有条件即可实施；&#8221;效益&#8221;是指能产生立竿见影的效果。</p>
<p>    本文所援引的范例项目，模拟最糟糕的开发团队，最混乱的开发流程：由很少写代码的测试和预研部门开发，人员不固定，时间也不固定，谁有空就写上一些；没有设计，没有文档，基至也不在代码文件中保存编码人员的信息，成员完全依赖于阅读代码和测试用例来理解其他成员写的代码；除了简单的命名规则外，没有其他规范，甚至连一个函数原则上不能超过50行之类的基本规范也没有（范例中有超过200行的函数CMacro::Unwind()，一万多条路径）。 任何开发团队和开发流程都会好于范例项目的开发团队和开发流程，因此，范例所展示的方法和工具，具有&#8221;广泛可行性&#8221;。</p>
<p>    本文介绍如何进行高效编码调试和实现彻底的单元测试。编码调试是任何软件开发都无可逃避的工作，在Visual Unit的支持下编码调试，只是把本来就一定要做的工作改变一下方式，不需要多做什么，就可以大幅提高编程效率和质量；另一方面，Visual Unit彻底改变了单元测试难于实施或成本昂贵的局面，无论团队中开发与测试人员的比例是怎么样的，都可以轻松快捷地实现彻底的单元测试。 </p>
<p>　<br />
二、高效编码调试</p>
<p>    任何软件开发，都离不开编码调试。对于稍为复杂一点的函数，一般来说，编写几行代码，就要执行一下，看它们是否按预想的工作，然后再继续写，写完后还要将各种可能输入都执行一下。如何执行？一般由别的代码来调用，也就是说需要驱动，驱动通常是在开始编写函数实现代码之前建立，这样才能一边编写一边调试。驱动大致可分为自然驱动和专门驱动。</p>
<p>    自然驱动：利用项目中已有的代码作为驱动，通常是在被调试的函数中加断点，从界面执行一个需要调用该函数的功能，调试器中断时就可以调试了；专门驱动：为需要调试的函数编写专门的驱动代码，通过执行驱动代码来执行被调试函数。</p>
<p>    自然驱动的主要优点是不需要其他工作就可以直接调试，甚至感觉不到需要驱动，主要缺点是输入数据通常是公共的，即很多代码都使用相同的输入源进行调试，实际输入往往是经过其他代码处理后的中间结果，要针对各种可能输入都进行调试往往很困难，造成调试不全面，程序员的思维受到局限，难于做到全面地考虑各种可能输入。</p>
<p>    专门驱动的主要优点是输入数据是专门针对于被测试程序，容易做到比较全面，程序员的思维也会比较全面，对编写功能齐全的健壮的程序很有好处，要针对某种特定输入进行调试比较容易，缺点是需要花费大量的时间来编写驱动代码。</p>
<p>    显然，自然驱动的主要问题是不全面，代码错误较多，专门驱动的主要问题是编写驱动代码很费时。有没有更好的方法，既不需要编写驱动代码，又能方便且全面地调试？有 ！这就是自动驱动，即在Visual Unit的支持下编码调试，不但无需费时间写驱动代码，更拥有多种独特的便利，可以大幅提高编码调试的质量和效率。<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
<br />
    Visual Unit是单元测试工具，但也是高效编程调试的支持环境，在Visual Unit的支持下调试，既全面又省时：<br />
    自动生成驱动代码，但又可以方便地设定调试输入；<br />
    测试用例编辑器列出全部输入，可以很方便地检查是否全面。<br />
    除了上述优点外，在Visual Unit的支持下调试，还可以：<br />
    可视化地选择调试输入；<br />
    调试过程中还可以切换输入；<br />
    无限制的后退，重复。</p>
<p>    上述仅是免费的个人版的功能，对于企业版用户，实际上大多数单步调试都可以省略：<br />
    自动输出参数、成员变量的输入输出值，返回值，用户也可以用简单的语法输出任何变量或表达式的值，这些数值都是上下文相关的；<br />
    显示在一个用例下，程序所执行的代码，可以很方便地查看程序是否按预想的流程执行。<br />
    程序无论多复杂，无非就是执行一些代码，读写、计算一些数据，因此，上述两方面信息已完整地描述了程序行为，一眼就能看出程序干了什么，通常可以很快判断程序是否按预想的工作并找到出错原因，比单步调试要快得多。</p>
<p>    下面以实例来进一步分析三种调试方式的优缺点。这里所用的示例是范例项目中的CExFunction::ParseOneParameter()函数，这是一个很普通的函数，读者也可以随便拿其他有些复杂度的代码来比较。该函数的功能是解析C++代码中的一个参数，原形如下：<br />
PARAMETER* CExFunction::ParseOneParameter(CTokenList&#038; iList);<br />
PARAMETER 是保存一个参数对象的结构，定义如下：<br />
     struct PARAMETER</p>
<p>     {</p>
<p>     CString type; //参数类型</p>
<p>     CString name; //参数名</p>
<p>     CString defVal; //缺省值</p>
<p>     CString array; //如果参数是数组，保存[]及[]内的文字常量</p>
<p>     };</p>
<p>    参数iList是一个输入参数（范例的命名规则是用i表示输入参数），传递由C++代码中的一个参数经过词法分析转换获得的记号对象序列，例如参数int* pi，将转换为三个记号对象，分别对应于：int, *, pi。该函数将记号对象序列解析到一个PARAMETER结构的指针中，并作为返回值返回。</p>
<p>    在这个示例中，如果要进行比较全面的调试，输入至少要考虑以下可能：<br />
    普通输入，如int i；<br />
    类型中有符号，如int* pi；<br />
    类型中有多个符号，如int*&#038; pi；<br />
    模板类，如CList<int, int> list；<br />
    带缺省值，如int i=0；<br />
    数组，如int ai[10]；<br />
    类型有多个单词，如const unsigned int&#038; i；<br />
    缺少参数名，如const int；</p>
<p>    我们在编写这个函数的实现代码前，首先建立调试驱动，以例边编码边执行调试。</p>
<p>自然驱动</p>
<p>    假设界面和要调用这个函数的其他代码都已完成。在函数的入口处插入断点，以调试方式运行工程，在界面中选择要生成文档的工程目录，点击&#8221;生成文档&#8221;，程序中断时就可以调试了。这种方式相信所有程序员都很熟悉，并且很多人都会认为这种方式最省时，但实际上，这种方式只是开头省时，当你试图把各个可能输入都调试一遍，就会发现它很费时：可能输入通常分散分布于输入源中（这里的输入源是代码文件），如果要比较全面地调试，通常要整理输入源，否则几乎不可能全面地调试，也就是说，要全面地调试，仍然要费时间整理输入，并不能完全依赖自然输入；<br />
    要针对某种输入进行调试，例如要调试参数带有缺省值的情形，一般通过反复跟踪直到想要的输入出现，或者设置条件断点拦截所需要输入，反复跟踪当然费时不少，设置条件断点也是要花时间的，并且有时无法满足需要 ，很多时候，要针对特殊输入进行调试都是很大费时的；<br />
    由于是公共输入源，输入数据很难管理，尤其是条件断点更不可能无限期地保存，以后需要再次调试时可能要做很多重复工作。<br />
    如上所述，自然驱动并不省时，不过这种方式的时间消耗隐藏在调试过程中，通常不会引起重视，其实&#8221;隐藏于调试过程中&#8221;，其成本更大，因为分散了开发人员的注意力，影响思维的连续性。<br />
    自然驱动的更主要问题是不全面，开发人员常常会将思维局限于现有的输入源，导致一些可能输入根本就没有考虑到，在本例中，很可能只是试一下解析一两个文件，检查得到的结果是否正确，如果文件中没有 带数组的参数，那这种输入很可能就被忽略掉。这种不全面，导致代码功能不齐，健壮性差，后期测试和维护成本居高不下，甚至导致项目的失败，因此，这是看起来高效，实际上很低效的方式，读者可以在看完后面两种方式的介绍后，自 已尝试并对比一下，可以拿任何有一定复杂度的代码编写来对比，不局限于这里所举的例子。</p>
<p>专门驱动</p>
<p>    专门驱动离单元测试只有一步之遥了，只要在驱动代码中添加判断预期输出的语句就构成了完整的测试代码，因此，在实际工作中，采用专门驱动最好是边编码边测试，并使用测试代码作为调试驱动。下面是为函数CExFunction::ParseOneParameter ()编写的调试驱动代码：<br />
     {CExFunction* pObj = new CExFunction();</p>
<p>     CTokenList iList;</p>
<p>     CTokenReader reader;</p>
<p>     reader.ReadTokenList(iList, &#8220;int i&#8221;);</p>
<p>     PARAMETER* ret = pObj->ParseOneParameter(iList) ;</p>
<p>     ASSERT( ret->type == &#8220;int&#8221; );</p>
<p>     ASSERT( ret->name == &#8220;i&#8221; );</p>
<p>     reader.ClearTokenList(iList); </p>
<p>     delete pParam;</p>
<p>     delete pObj;}</p>
<p>    上面的代码其实是一个测试用例的完整代码，测试代码通常都是很简单的，功能无非是使被测试的代码得于执行，被测试代码通常都涉及到外部数据，如参数、成员变量、全局变量什么的，这些数据当然要设定初始值，例如，上面的测试用例是将字符串&#8221;int i&#8221;经过CTokenReader对象的ReadTokenList方法转换成CToken对象指针的列表作为参数iList的输入。</p>
<p>    在实际工作中，函数的输入输出常常不是简单的数据类型，而是某些对象甚至是对象的集合，本例中，输入的数据就是CToken对象 指针的列表。这种情况下， 一般借助现有的代码来生成数据，通常，这些&#8221;现在代码&#8221;都是存在的，因为即使不做测试，也总有代码要调用该函数的，调用代码本来就需要生成相应的数据。本例中，CTokenReader::ReadTokenList()函数就是把字符串转换为CToken对象指针的列表。</p>
<p>    只要写完了第一个测试用例的代码，更多的用例就简单了，只要拷贝并对输入输出数据进行修改就行。细心的读都可能已注意到，第一个测试用例的前后加了{}，这是为了多个测试用例可以使用相同的变量名。</p>
<p>    使用这种方法，建立测试代码通常是很快的。编写很简单的函数时不需要调试，当然也不需要测试代码。测试代码的组织也很简单：一个产品工程对应一个测试工程，一个产品类对应一个测试类，一个需要测试的产品函数对应一个测试函数。测试工程可以加一个简单的界面，以便执行指定的测试，也可以使用相应的工具如CppUnit。</p>
<p>    再回到我们的主题：调试。有了测试代码，调试就简单了，要调试某种输入，只要在相应的测试用例中加断点就行了。使用这种方式，仅就调试来说，好处也是非常明显的：<br />
    所有输入在一起列出来，调试比较全面，程序员的思维也会比较全面；<br />
    要调试指定的输入很容易，通常不需要高级断点，更不需要通过反复跟踪来捕捉需要的输入；<br />
    调试数据可以永久保存，避免了以后修改代码时的重复工作。</p>
<p>自动驱动</p>
<p>    在Visual Unit的支持下编码调试，除了兼具自然驱动和专门驱动的优点外，还能享受Visual Unit的独特殊功能带来的便利。</p>
<p>    首先我们用个人版来说明，个人版是免费的版本，并且开发商也提供免费的基本技术支持。</p>
<p>    Visual Unit具有丰富的文档，包括视频教程，这里不再叙述其基本使用方法。只要选择要测试(调试)的类和函数(如果使用企业版的IDE插件，会根据当前文档和光标位置自动选择)，VU就会生成测试代码，并弹出测试用例编译器。VU是自动生成测试代码，而不是自动生成测试用例，也就是说，输入输出数据是由用户指定的，不过VU已经生成了输入输出数据的声明。下面是本例中VU生成的第一个测试用例的输入输出：</p>
<p>输入部分：<br />
CTokenList iList = </p>
<p>输出部分：<br />
ret ==</p>
<p>    这里的“=”和“==”仅仅表示可能需要赋值或判断输出，对于基本数据类型，可以直接填写数值，高级数据类型需要灵活处理(详细信息请查看帮助或教程)。只要把输入输出改为这样就完成了第一个测试用例的建立：</p>
<p>输入部分：<br />
     CTokenList iList = //多余的=会自动删除</p>
<p>     CTokenReader reader</p>
<p>     reader.ReadTokenList(iList, &#8220;int i&#8221;)</p>
<p>输出部分：<br />
     ret->type == &#8220;int&#8221;</p>
<p>     ret->name == &#8220;i&#8221;</p>
<p>    更多的测试用例，只要点击&#8221;新建&#8221;，就会自动拷贝当前用例，只要修改输入输出就行了。这里没有涉及到成员变量和变局全量，不过都很简单的(成员变量用点操作符访问，全局变量直接访问)，请查看帮助。</p>
<p>    可以看出，使用VU建立调试支持环境是很快速的：对于第一个测试用例，输入输出比较复杂时需要写少量简单的代码，输入输出简单时直接填写输入输出数值，其他测试用例只需点击一个按钮拷贝现有测试用例并修改输入输出就行，可以选择相近的用例来生成新的用例，这样通常只需要修改一两个数据就可以得到想要的用例。</p>
<p>    那么，还可以得到哪些好处呢？<br />
    方便地进入调式：在被调试函数的入口加断点，并调试测试工程即可进入调试；<br />
    方便地选择输入：在测试用例编辑器中轻点鼠标即可指定要调试的输入，如果执行了测试，只要点击出错的测试，就会自动选择相应的输入；<br />
    方便地切换输入：调试过程中，不需要退出调试，就可以切换到其他输入：只要在测试用例编辑器中选择另一个测试用例，用调试器的&#8221;执行到光标所在行&#8221;命令回到函数入口，即可切换到新的输入；<br />
    无限制后退重复：用调试器的&#8221;执行到光标所在行&#8221;命令可以自由地后退和重复执行，其实现的原理是&#8221;重来&#8221;，后退时相关数据也会&#8221;还原&#8221;，感觉上是真正的&#8221;后退&#8221;，这个奇特而有用的功能是VU生成的测试代码自动实现的。</p>
<p>    上述是免费的个人版所具有功能，VU企业版除了具有这些功能外，还具有&#8221;描述程序行为&#8221;的功能：<br />
    自动输出参数、成员变量的输入输出值，返回值，用户也可以用简单的语法输出任何变量或表达式的值。这些数值都是上下文相关的，也就是说，同一个用例的相关值放在一起；<br />
    显示在任一个用例下程序所执行的代码，可以很方便地查看程序是否按预想的流程执行。<br />
    程序无论多复杂，无非就是执行一些代码，读写、计算一些数据，因此，上述两方面信息已完整地描述了程序行为，很容易看出&#8221;程序干了什么&#8221;。这个功能大幅度地提高了开发人员的工作效率：<br />
    帮助整理、验证编程思路：写几行代码，就可以看看&#8221;程序干了什么&#8221;，轻松判断&#8221;现在所写的对不对&#8221;，也比较容易想清楚&#8221;接下来要怎么写&#8221;。<br />
    快速找出程序错误：根据输入输出数据和所执行的代码，通常可以很快判断程序是否按预期工作并找到出错原因，比单步调试要快得多。<br />
    编程的时间消耗主要不在于敲键盘，而在于编程思路和调试，VU企业版可谓&#8221;对症下药&#8221;，在这两方面大量提高工作效率。</p>
<p>    以上所述，都是针对软件开发过程中无可逃避的工作：编码调试。仅从时间上来说，对于编写调试有一定复杂度的程序，使用专门驱动，可能比自然驱动多费一点时间，但也是完全合算的，至于自动驱动，如果使用VU个人版，大概能省时10%，如果使用企业版，则可以省时20-50%!读者可以自行尝试比较一下。是否省时还不是最重要的，更高的价值在于：使用专门驱动或自动驱动编码调试，实际上也已经把令人望而生畏的单元测试工作完成了一半，并清除了实施单元测试的最主要障碍。</p>
<p>　<br />
三、实现彻底单元测试</p>
<p>    是什么使单元测试难于实施？首先是代码的可测性。可测性是什么？如果一个类具有基本的可测性，那么把它加入到另一个工程后（当然有关联的文件也要加入）能够通过编译，这其实是很低的要求，但对于一个有一定规模的项目，如果开发调试时使用自然驱动，在完成编码后才进行单元测试，那么通常都不具有可测性，因为开发人员常常在无意之中使代码之间产生了不当耦合，这些不当耦合累积起来，会使整个项目的代码纠缠在一起，造成难于测试。</p>
<p>    使单元测试难于实施的另一个方面是建立测试用例。在本例中，如果由不熟悉代码的测试人员建立测试用例，那么他很可能不知道如何生成CToken对象 指针的列表。</p>
<p>    如果边开发边使用专门驱动(测试代码放在另一个工程中)或自动驱动调试，那么一旦出现影响可测性的不当耦合就会及时发现及时解决，保证了代码的可测性，另一方面，由于至少建立了一个测试用例，测试人员建立其他用例时只要修改一下输入输出数据，从而大大降低建立测试用例的难度。总之，使用专门驱动或自动驱动调试，在不增加开发工作量的同时，已经为单元测试打一下了坚实的基础。</p>
<p>    范例项目V0.1处于这样一个阶段：刚刚完成代码编写，并未完成单元测试，现有的多数测试用例都是编码时用于调试的。在此基础上，单元测试由谁做，难度都不大。VU的典型应用是通过三个 阶段来完成针对一个函数的彻底的测试：<br />
    1）基本功能测试：测试代码的基本功能；<br />
    2）完成白盒覆盖：在现有用例的基础上，使用测试用例设计器为未覆盖的语句、条件、分支、路径设计测试用例，达到100%语句、条件、分支、路径覆盖；<br />
    3）执行自动边界测试捕捉意料之外的错误。<br />
    以上三阶段可以由不同人员在不同时间完成，团队可以根据实际情况做出灵活安排，下面是一个典型的开发测试流程。<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
<br />
    1）开发人员边开发边使用VU调试测试，完成基本功能测试。在VU的支持下开发调试，可以大幅提高开发效率，绝不会影响开发进度。也许读者会认为，开发人员没有时间去设计测试用例，其实这是一种误解，开发人员写代码时肯定要想清楚代码的功能并且要使用基本的输入进行调试，这些就是基本的测试用例，实际上不需要多做什么。开发人员提交代码同时提交测试代码和测试报告。如果项目已完成或部分完成编码，也建议先由程序员首先对重要代码进行基本的功能测试。<br />
    2）测试人员检查基本测试用例是否符合设计，并在此基础上完成白盒覆盖和边界测试。由于有了初步的测试，保证了代码可测性，不可能产生因为不当耦合造成难于测试的状况；在现有用例的基础上，使用测试用例设计器找出遗漏用例也不会有太大障碍，这就使测试人员的工作易于进行。测试人员只需要提交更新过的测试代码和测试报告，不需要另外记录测出的问题。<br />
    3）开发人员下载新的测试代码和测试报告，执行整体测试，然后针对报告了错误的函数执行函数测试以获取详细信息，必要时进行调试，找出错误，修改代码，使所有测试通过，再次提交产品代码和测试报告。<br />
    4）测试人员再次执行整体测试，验证所有的测试均已通过。</p>
<p>    以上流程是动态和反复的，并不是编码完成后再单元测试。开发人员写完一个类后即可提交代码由测试人员完成白盒覆盖和边界测试。另外，团队可以根据开发与测试人员的比例调整工作份额，如果团队没有测试人员，那么由开发人员完成全部单元测试也是可行的。</p>
<p>    彻底的单元测试对于软件开发来说，其价值是难于估量的：除了保证局部代码的质量外，有了单元测试，任何时候修改代码后都可以通过回归测试来自动检查修改是否引入错误，这就使开发过程可以适应频繁变化的需求，系统分析、概要设计和详细设计都可以做得简单一些，也更能适应螺旋式开发过程，以后的维护升级成本也会大幅降低，同时，高质量的代码使集成测试和系统测试的工作量降低很多(实际上单元测试已包含了大部分的集成测试)，发现问题后的修改也会高效得多。总之，要提高软件开发质量、降低软件开发成本，最有效的改进就是进行彻底的单元测试，如果不进行单元测试，任何流程改进都无法保证产品质量，因为，程序始终是由代码构成的，代码的质量没有保证，软件的质量拿什么来保证？单元测试并不排斥其他过程改进，相反，单元测试对开发流程中的所有环节几乎都有促进作用。</p>
<p>    下边再谈谈关于白盒覆盖的话题。使用VU实施单元测试，100%的语句、条件、分支覆盖通常都是很容易的，路径覆盖有时候会很难，例如，我们所举的例了，CExFunction::ParseOneParameter ()，有九十多条路径，要覆盖似乎不现实或没必要，这种状况通常是设计不合理造成的，例如，CExFunction::ParseOneParameter ()函数的代码分为三块：1、解析缺省值，2、解析数组，3、解析类型和参数名，前两块解析了缺省值和数组后把相应的Token从列表中删除，这样的话，第3块与前两块是没有逻辑关系的，但是，这3块代码会组合出很多路径，没有逻辑关系的代码所组合出来的路径是没有意义的，这些代码具有&#8221;高耦合低内聚&#8221;的特征，不应该放在一个函数中。范例中另外写了一个函数：CExFunction::ParseOneParameter2()，把以上三块代码分别独立出来自成一个函数，这样 每个函数都能完成100%的路径覆盖，重构后的代码既易于测试，也易于维护。范例中有大量类似的函数，甚至有超过200行的函数，这是为了检验VU的适应能力，以后的版本是会进行重构。我们建议程序员完成编码后，检查一下路径数量，如果路径很多，代码很可能需要分拆，合理的路径数量应该是等价类数量的一至两倍。另外，从逻辑结构图也可以看出来：图中有两个或两个以上串联的复杂分支结构，往往表示代码的结构有问题。</p>
<p>    VU的逻辑结构图具有屏蔽对象的功能，因此，遇到上述情形时可以通过交替屏蔽部分分支结构的方式来实现路径覆盖，但这不是我们推荐的方式，因为它虽然可以保证测试的完整性，但并没有改进代码的结构。</p>
<p>    关于单元测试和范例项目，还有很多值得叙述的话题，例如内存泄漏测试以及一些复杂问题的处理等等，这里暂且不谈。最后谈谈已完成编码的项目的单元测试。对于已完成编码的项目，最好先由开发人员&#8221;去耦合&#8221;，方法是将代码文件从底层向上排列，按顺序依次将文件加入到另一个工程并编译，如果产生编译错误，则想办法消除编译错误 ，VU提供了文件排序工具，具体的使用方法请查阅帮助中《测试旧工程》部分。 完成“去耦合”后，由开发人员对自己编写的代码完成基本功能测试，测试人员完成白盒覆盖和边界测试。对于编码过程中使用自然驱动调试的已完成编写的代码，完全由测试部门进行单元测试通常是很难的。<br />
附：<br />
C++代码文档生成器（源代码下载）</p>
<p>    本软件的主要功能是解析C/C++代码，依据代码及注释自动生成代码文档。点击这里浏览本文档生成器依据自身代码生成的文档，文档格式为XML，需要XML解析器的支持，请允许ActiveX运行，如仍无法浏览，请下载安装微软的XML插件。</p>
<p>    这是凯乐软件测试部主持的一个开源项目，模拟最糟糕的开发团队，最混乱的开发流程，以恶劣环境下的实战检验和展示Visual Unit在软件开发和测试中的应用。此项目由很少写代码的测试和预研部门开发，人员不固定，时间也不固定，谁有空就写上一些；没有设计，没有文档，基至也不在代码文件中保存编码人员的信息，成员完全依赖于阅读代码和测试用例来理解其他成员写的代码；除了简单的命名规则外，没有其他规范，甚至连一个函数原则上不能超过50行之类的基本规范也没有（范例中有超过200行的函数CMacro::Unwind()，一万多条路径）。总之，此项目所展示的编码调试方式和单元测试方法，是任何开发团队都可以实施的。详细信息，请阅读相关文档：《高效开发与彻底测试》。</p>
<p>代码文档生成器使用说明</p>
<p>1) 不需要安装，双击启动；<br />
2) 选择要生成文档的项目目录，可以选择多个目录；<br />
3) 在编译条件输入框中添加编译条件（可选）；<br />
4) 选择文档的保存目录；<br />
5) 点击&#8221;生成&#8221;，即可生成XML文档；<br />
6) 将XSL目录下的文件拷贝到文档目录，点击index.htm即可浏览文档；<br />
7) 通过修改XSL目录下的文件可以定制显示格式或装饰页面。</p>
<p>项目源代码使用说明</p>
<p>源代码包括产品工程和测试工程:<br />
产品工程目录：/源代码/Docer/<br />
测试工程目录：/源代码/TestDocer/</p>
<p>安装Visual Unit1.3，启动VC6，将产品工程目录及Visual Unit安装目录/include/添加到搜索路径，更详细的信息请查看Visual Unit帮助《新建测试工程-VC6》。</p>
<p>产品工程可以使用VC6直接编译。</p>
<p>测试工程的编译运行：<br />
1）启动Visual Unit(个人版或企业版)，导航窗口->菜单->目录，选择产品工程和测试工程的目录；<br />
2）试一下编译测试工程，如果通不过编译，请查看帮助《编译错误》章节的说明；<br />
3）在导航窗口选择要测试的源文件，如产品工程目录下的ExFunction.cpp文件；<br />
4）在导航窗口函数列表中选择一个函数，如ParseOneParameter，编译并执行测试工程（Execute Program或Ctrl+F5），即可执行测试。<br />
5）企业版用户可以使用IDE插件，自动选择要测试的文件和函数，请查看帮助中《使用IDE插件》章节。<br />
6）要调试程序，添加断点，如在CExFunction::ParseOneParameter()入口处加断点，执行调试(Go或F5)，即可进入调试，调试前可以打开测试用例编辑器选择调试输入(测试用例)，调试过程中也可以切换用例，请参考帮助《排错&#8211;调试》。企业版用户，可以直接执行IDE插件的调试命令，无需手工断点。<br />
7）大多数代码（比较简单的除外）都是边开发边在Visual Unit的支持下调试的，不需要界面，随便选择一个函数都可以立即进入调试，也可以随便增删修改调试输入（使用测试用例编辑器）。<br />
8）目前版本仅实现了最基本功能，为了检验和展示VU的适应能力，很多代码不完善或有重构的必要，同时也有很多测试用例不完整。如果您有意扩展 本项目的功能，请在Visual Unit的支持下编码调试，即使是免费的个人版，也能大幅提高开发的效率和质量。 　<br />
　<br />
变量命名规则</p>
<p>m 成员变量<br />
g 全局变量<br />
s 静态变量<br />
p 指针<br />
i 输入参数<br />
o 输出参数</p>
<p>示例<br />
CString name; //局部变量<br />
CStrin mName; //成员变量<br />
CString iName; //输入参数<br />
CString& oName; //输出参数<br />
CString gName; //全局变量<br />
CString* pName; //局部变量，指针<br />
CString* mpName; //成员变量，指针<br />
CString* ipName; //输入参数，指针<br />
CString* opName; //输出参数，指针<br />
CString* iopName; //输入输出参数，指针</p>
]]></content:encoded>
			<wfw:commentRss>http://www.evanjiang.net.cn/archives/712.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>VC 代码编写 常识</title>
		<link>http://www.evanjiang.net.cn/archives/247.html</link>
		<comments>http://www.evanjiang.net.cn/archives/247.html#comments</comments>
		<pubDate>Fri, 16 Jan 2009 07:27:24 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[vc++/vc#]]></category>
		<category><![CDATA[vc 代码编写 常识]]></category>

		<guid isPermaLink="false">http://www.hunttech.com.cn/wpblog/?p=247</guid>
		<description><![CDATA[<p>一 程序设计
　　要避免错误，首先要从好的设计开始。对于程序的设计，需考虑到程序的两个特性：
　　1 简单性
　　大多数常见的错误来源于程序设计中不必要的复杂成分。一个好的设计应该反映问题本身的要求，而不必为了刻意追求“满足将来的需要”而添加不必要的特性。实际上，简
单优雅的设计比那些复杂的设计更能迎合未来的需求。
　　2 耦合性
　　耦合(decoupling)性用来衡量不同对象之间的依赖程度。松耦合的程序易于理解和实现，易于测试和维护，且这种程序包含错误的可能性小，错误也较容易发现和清除。
二 编程风格
　　编程风格是个人问题，有很大的随意性。一个好的编程风格不仅让代码易理解，也易于调试。好的编程风格包括：
　　1 清晰地书写代码
　　如果没有必要，尽量不要使用语言中的高级特性，因为这些特性不易于理解和调试。使用大多数程序员都能理解的语言成分来书写代码不易犯错且易于理解和维护。
　　2 编写结构良好的代码
　　当程序崩溃时所能得到的最基本的调试信息是源代码文件、问题所在行的行号和一个调用栈(call stack)。调用栈是调试程序时最有帮助的部分，它提供错误出现的上下文，
也就是带参数的函数调用序列。你书写的代码结构越好，调用栈就能给你越多信息。</p>
<p></p>
<p>　　3 使用良好的标识符
　　一个好名字能使你的代码更容易被理解和维护。流行的匈牙利命名法(Hungarian Notation)实际上是把标识符的意义和表示方法结合起来。现在，匈牙利命名法表现出不少的
局限性，匈牙利命名法过于看重前缀的作用，对一个变量的表达信息不完整，实际上并没有传递多少有用信息，它使代码难于阅读，难以维护。一个好的命名传统是指示出变量的
作用域以便在需要的时候检查它的定义，并明确地指出一个变量是全局的、局部的还是成员数据。依赖变量的定义比依赖匈牙利前缀更加有用和可靠。
　　好的名字能够用平常的语言概括出该标识符所代表的实体的含义。在选择类、函数、变量的名字时可以考虑以下几个原则：
　　　　取简单的描述性名字，好的名字能简要地概括出这个标识符代表的含义。
　　　　避免简写，简写使标识符难于阅读和记忆，尽量使用混合大小写的完整的单词。
　　　　避免相似性的文字，避免混淆。
　　　　避免采用一般的或随机产生的名字，而应采用有实际意义的名字。如欲从按钮类派生位图按钮，取一个CBitmapButton，而不是CMyButton。
　　4 用简单的语句行
　　在VC中，一行可写多个语句。但调试是面向行的，过于复杂的行难于调试。因此，从调试的角度出发，每一个语句都应独自成行。
　　5 使用统一的排列
　　统一的排列方式使类、变量的定义和语句更加明显。
　　6 用括号使书写清晰
　　你不一定能都记住各种运算符的优先级和结合律，而使用多余的括号并不影响编译后的代码。因此，如果你不能确定是否需要括号时，请加上它。
　　7 使用好的注释
　　用好的注释能使你的代码不易出错，而且便于其他程序员阅读，便于理解和维护。
三 编写程序时应注意的问题
　　1 充分利用VC++的特性
　　可用下列技术来充分利用VC++的编译器的特性：
　　（1）用const代替#define来创建常量；
　　（2）用enum代替#define来创建常量集合；
　　（3）用内联(inline)函数代替#define；
　　这三种技术用C++而不是C预处理。使用预处理的问题在于编译器对于预处理器所作的事情一无所知，因此无法用数据类型检查错误和不一致的地方。预处理的名字不在符号表
里，因此也不能用调试工具来检查预处理常量。相似地，预处理宏被编译进去，不能用调试工具跟踪。编译器能充分了解const、enum和inline语句，从而能在编译的时候对出现的
问题发出警告。
　　但预处理在很多调试代码中起重要作用。调试代码经常需要从非调试代码里面得到不同的行为，而最有效的办法就是让预处理为调试创建不同的代码。
　　（4）用new和delete代替malloc和free;
　　在创建对象、类型的安全性和灵活性方面。使用new/delete比malloc/free要好。另外，new可被重载，提供了更大的灵活性。








　　（5）用输入输出流(iostreams)代替stdio。
　　使用C++输入输出流（）而不使用C标准输入输出库（printf/sprintf和scanf/sscanf），有利于安全性和扩展性。从调试的角度来看，标准输入输出函数的最大问题在
于编译器不能对控制流参数进行任何类型检测，而输入输出流的任何问题都能在编译时检测出来。
　　2 使用头文件
　　要在头文件中声明所有共享的外部符号，而且保留函数原型中的参数名。把所有的共享定义放在头文件中，不要在.cpp文件里面看到extern关键字。
　　3 初始化变量
　　在使用变量之前一定要把它们初始化。在初始化之前就使用变量肯定会产生错误。通常不需对对象进行初始化，对对数据成员应在构造函数中初始化。必须明确地为在栈中和
堆中分配的数组和数据结构进行初始化。对于对象，应该初始化每个需要初始化的数据成员。因为变量的使用是由优化器来检查的，所以检测未初始化的本地变量，发布版本要比
调试版本要做得好。
　　4 使用布尔表达式
    C++的布尔类型：bool，值为true和false，大小为一个字节。
    Windows程序通常用BOOL类型。定义如下：
      Typedef int BOOL;
      #define FALSE 0
      #define TRUE 1
　　在C++中，一个布尔表达式如果为0则为假，其他则为真。因此，对布尔表达式应该检查是否问假而不是检查是否为真。
　　5 使用句柄和指针
　　初始化一个指针时，要么让其指向一个有效的内存地址，要么设为0（空指针），避免指针指向无效地址。回收指针所指对象时要重新初始化这个指针，并且在指针被释放前为
空时就对其进行处理。对句柄的处理跟指针一样。
　　6 用引用而不是指针做参数
　　用指针做函数的参数可传递一个空指针，很灵活，但也很容易忘了对指针进行初始化。而引用是对象的别名，它必须和有效的对象相关联，不存在空的和没有初始化的引用。
当在函数中收到一个引用参数时，可以肯定这是一个有效的对象。程序用引用做参数比用指针做参数更为健壮。
　　7 强制类型转换(cast)
　　进行数据类型的强制类型转换时，将会调用相应的构造函数或转换函数来创建一个新类型的临时对象。对指针的正确类型转换可消除一个编译错误，但并没改变指针。强制类
型转换破坏了编译器进行类型检查的功能，而这正是编译器查找错误的最有效的机制。为了保证安全性，每一个强制类型转换都需要手工进行类型检查。为尽量避免强制类型转换
，你可以：避免使用多态数据类型；使用更加广泛的基类；提供特殊的存取函数；让编译器隐式处理类型转换等措施。
　　8 使用构造函数和析构函数
　　构造函数需要分配内存，创建资源或者打开文件，这些运算并不总是成功。构造函数没有返回值，没有直接显示错误的方法。一个常见的方法（在很多MFC类中使用）是把对象
创建分为两步：第一步，让构造函数以一种不会出错的方式初始化对象；第二步，让某些初始化函数（如Init或Open）完成工作，这一步可能出错。另一种方法是在构造函数中使
用异常：第一步，以不会出错的方式初始化对象；第二步，用可能在try段内出错的代码初始化对象；第三步，在catch代码里面处理异常。如果出现异常，就会在构造函数里清除
分配的资源，并且再次抛出异常。








　　异常处理的一个关键细节就是在栈展开的过程中抛出的异常会终止整个应用程序。在处理异常时经常要调用析构函数，因此析构函数很容易出错，一定要保证析构函数的异常
在析构函数中得到处理。要保证基类的析构函数是虚函数。这样，就算对象是一个指向基类的指针，也会调用派生类的析构函数。否则，就会引起资源泄漏(resource [...]]]></description>
			<content:encoded><![CDATA[<p>一 程序设计<br />
　　要避免错误，首先要从好的设计开始。对于程序的设计，需考虑到程序的两个特性：<br />
　　1 简单性<br />
　　大多数常见的错误来源于程序设计中不必要的复杂成分。一个好的设计应该反映问题本身的要求，而不必为了刻意追求“满足将来的需要”而添加不必要的特性。实际上，简<br />
单优雅的设计比那些复杂的设计更能迎合未来的需求。<br />
　　2 耦合性<br />
　　耦合(decoupling)性用来衡量不同对象之间的依赖程度。松耦合的程序易于理解和实现，易于测试和维护，且这种程序包含错误的可能性小，错误也较容易发现和清除。<br />
二 编程风格<br />
　　编程风格是个人问题，有很大的随意性。一个好的编程风格不仅让代码易理解，也易于调试。好的编程风格包括：<br />
　　1 清晰地书写代码<br />
　　如果没有必要，尽量不要使用语言中的高级特性，因为这些特性不易于理解和调试。使用大多数程序员都能理解的语言成分来书写代码不易犯错且易于理解和维护。<br />
　　2 编写结构良好的代码<br />
　　当程序崩溃时所能得到的最基本的调试信息是源代码文件、问题所在行的行号和一个调用栈(call stack)。调用栈是调试程序时最有帮助的部分，它提供错误出现的上下文，<br />
也就是带参数的函数调用序列。你书写的代码结构越好，调用栈就能给你越多信息。</p>
<p><span id="more-247"></span></p>
<p>　　3 使用良好的标识符<br />
　　一个好名字能使你的代码更容易被理解和维护。流行的匈牙利命名法(Hungarian Notation)实际上是把标识符的意义和表示方法结合起来。现在，匈牙利命名法表现出不少的<br />
局限性，匈牙利命名法过于看重前缀的作用，对一个变量的表达信息不完整，实际上并没有传递多少有用信息，它使代码难于阅读，难以维护。一个好的命名传统是指示出变量的<br />
作用域以便在需要的时候检查它的定义，并明确地指出一个变量是全局的、局部的还是成员数据。依赖变量的定义比依赖匈牙利前缀更加有用和可靠。<br />
　　好的名字能够用平常的语言概括出该标识符所代表的实体的含义。在选择类、函数、变量的名字时可以考虑以下几个原则：<br />
　　　　取简单的描述性名字，好的名字能简要地概括出这个标识符代表的含义。<br />
　　　　避免简写，简写使标识符难于阅读和记忆，尽量使用混合大小写的完整的单词。<br />
　　　　避免相似性的文字，避免混淆。<br />
　　　　避免采用一般的或随机产生的名字，而应采用有实际意义的名字。如欲从按钮类派生位图按钮，取一个CBitmapButton，而不是CMyButton。<br />
　　4 用简单的语句行<br />
　　在VC中，一行可写多个语句。但调试是面向行的，过于复杂的行难于调试。因此，从调试的角度出发，每一个语句都应独自成行。<br />
　　5 使用统一的排列<br />
　　统一的排列方式使类、变量的定义和语句更加明显。<br />
　　6 用括号使书写清晰<br />
　　你不一定能都记住各种运算符的优先级和结合律，而使用多余的括号并不影响编译后的代码。因此，如果你不能确定是否需要括号时，请加上它。<br />
　　7 使用好的注释<br />
　　用好的注释能使你的代码不易出错，而且便于其他程序员阅读，便于理解和维护。<br />
三 编写程序时应注意的问题<br />
　　1 充分利用VC++的特性<br />
　　可用下列技术来充分利用VC++的编译器的特性：<br />
　　（1）用const代替#define来创建常量；<br />
　　（2）用enum代替#define来创建常量集合；<br />
　　（3）用内联(inline)函数代替#define；<br />
　　这三种技术用C++而不是C预处理。使用预处理的问题在于编译器对于预处理器所作的事情一无所知，因此无法用数据类型检查错误和不一致的地方。预处理的名字不在符号表<br />
里，因此也不能用调试工具来检查预处理常量。相似地，预处理宏被编译进去，不能用调试工具跟踪。编译器能充分了解const、enum和inline语句，从而能在编译的时候对出现的<br />
问题发出警告。<br />
　　但预处理在很多调试代码中起重要作用。调试代码经常需要从非调试代码里面得到不同的行为，而最有效的办法就是让预处理为调试创建不同的代码。<br />
　　（4）用new和delete代替malloc和free;<br />
　　在创建对象、类型的安全性和灵活性方面。使用new/delete比malloc/free要好。另外，new可被重载，提供了更大的灵活性。<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
<br />
　　（5）用输入输出流(iostreams)代替stdio。<br />
　　使用C++输入输出流（<<和>>）而不使用C标准输入输出库（printf/sprintf和scanf/sscanf），有利于安全性和扩展性。从调试的角度来看，标准输入输出函数的最大问题在<br />
于编译器不能对控制流参数进行任何类型检测，而输入输出流的任何问题都能在编译时检测出来。<br />
　　2 使用头文件<br />
　　要在头文件中声明所有共享的外部符号，而且保留函数原型中的参数名。把所有的共享定义放在头文件中，不要在.cpp文件里面看到extern关键字。<br />
　　3 初始化变量<br />
　　在使用变量之前一定要把它们初始化。在初始化之前就使用变量肯定会产生错误。通常不需对对象进行初始化，对对数据成员应在构造函数中初始化。必须明确地为在栈中和<br />
堆中分配的数组和数据结构进行初始化。对于对象，应该初始化每个需要初始化的数据成员。因为变量的使用是由优化器来检查的，所以检测未初始化的本地变量，发布版本要比<br />
调试版本要做得好。<br />
　　4 使用布尔表达式<br />
    C++的布尔类型：bool，值为true和false，大小为一个字节。<br />
    Windows程序通常用BOOL类型。定义如下：<br />
      Typedef int BOOL;<br />
      #define FALSE 0<br />
      #define TRUE 1<br />
　　在C++中，一个布尔表达式如果为0则为假，其他则为真。因此，对布尔表达式应该检查是否问假而不是检查是否为真。<br />
　　5 使用句柄和指针<br />
　　初始化一个指针时，要么让其指向一个有效的内存地址，要么设为0（空指针），避免指针指向无效地址。回收指针所指对象时要重新初始化这个指针，并且在指针被释放前为<br />
空时就对其进行处理。对句柄的处理跟指针一样。<br />
　　6 用引用而不是指针做参数<br />
　　用指针做函数的参数可传递一个空指针，很灵活，但也很容易忘了对指针进行初始化。而引用是对象的别名，它必须和有效的对象相关联，不存在空的和没有初始化的引用。<br />
当在函数中收到一个引用参数时，可以肯定这是一个有效的对象。程序用引用做参数比用指针做参数更为健壮。<br />
　　7 强制类型转换(cast)<br />
　　进行数据类型的强制类型转换时，将会调用相应的构造函数或转换函数来创建一个新类型的临时对象。对指针的正确类型转换可消除一个编译错误，但并没改变指针。强制类<br />
型转换破坏了编译器进行类型检查的功能，而这正是编译器查找错误的最有效的机制。为了保证安全性，每一个强制类型转换都需要手工进行类型检查。为尽量避免强制类型转换<br />
，你可以：避免使用多态数据类型；使用更加广泛的基类；提供特殊的存取函数；让编译器隐式处理类型转换等措施。<br />
　　8 使用构造函数和析构函数<br />
　　构造函数需要分配内存，创建资源或者打开文件，这些运算并不总是成功。构造函数没有返回值，没有直接显示错误的方法。一个常见的方法（在很多MFC类中使用）是把对象<br />
创建分为两步：第一步，让构造函数以一种不会出错的方式初始化对象；第二步，让某些初始化函数（如Init或Open）完成工作，这一步可能出错。另一种方法是在构造函数中使<br />
用异常：第一步，以不会出错的方式初始化对象；第二步，用可能在try段内出错的代码初始化对象；第三步，在catch代码里面处理异常。如果出现异常，就会在构造函数里清除<br />
分配的资源，并且再次抛出异常。<br />

<!-- Begin alimama Adserver code -->
<script type="text/javascript"><!--
google_ad_client = "pub-8438729971248494";
/* 728x90, ������ 10-2-7 */
google_ad_slot = "4752526529";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
<!-- End Alimama Adserver code -->
<br />
　　异常处理的一个关键细节就是在栈展开的过程中抛出的异常会终止整个应用程序。在处理异常时经常要调用析构函数，因此析构函数很容易出错，一定要保证析构函数的异常<br />
在析构函数中得到处理。要保证基类的析构函数是虚函数。这样，就算对象是一个指向基类的指针，也会调用派生类的析构函数。否则，就会引起资源泄漏(resource leak)。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.evanjiang.net.cn/archives/247.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
