您的位置:首页 > 编程语言 > C语言/C++

大型C++项目必须注意的几个小问题

2015-08-27 16:02 393 查看
大型C++项目必须注意的几个小问题

有些问题对于小型的C++项目来说可能无关紧要,但对于大中型C++项目来讲,这些问题却成了大问题。什么样的项目算是小型项目呢,什么样的算是大中型项目呢,我认为10万LOC以下为小型项目,10-50万LOC为中型项目,50万LOC以上为大型项目。当然,不能单纯地以代码行数作为衡量标准,前几天产品重构,我用四二三十行代码换掉了原来的三四千行代码,那这个项目的规模是用这二三十行来计算呢,还是用那三四千行算呢?软件很难有一个准确的度量标准,暂以行数作为一种参考性标准吧。

当项目较大量一些在小型项目中不需要考虑的问题也变得非常重要了,对这些问题还是要认真考虑的。

1. 目录结构
小型项目可能一个目录就够了,把所有头文件和源代码及相关的工程文件放到一起,但项目较在时目录中的文件也就多了起来,文件一多查看就很不方便,如果对各种文件执照功能或类别进行一些分组就会方便很多,比如查找某个文件也很迅速。这一点,我觉得boost和chrome做得是很好的,包括它们的命名规则都很统一,而ACE做得就不是太好,所有文件放到一个目录里,好几百个文件,如果安装了CVS或SVN的windows客户端,在打开资源管理器时还要扫描一下文件是否与版本库关联,使得反应其慢,感觉不是太好。

2. 模块划分
一个大型的项目不可能当成一个整体工程一下子编译完的,如果有几百个文件,使用一个Makefile或者VS的工程,每编译一次都会很耗时,如果需要经常调试的话,编译就会很消耗时间。如果把整个工程划分成多个模块,不同模块以静态库或者动态库的方式进行组织,各模块不仅可以分别编译,单独测试,而且对于多个人协作的并行开发是非常有利的。因为不论是VSS、CVS还要SVN等这些主流的版本管理软件对于并行开发都不能做到尽善尽美,其实这个也不可能,因为软件本身不可能识别程序,像人一样去合并程序代码,如果几个人会经常修改同一个模块的代码时冲突可能就比较多,解决冲突的次数多了不仅影响工作效率,而且还影响心情。

3. 头文件层次
为什么把头文件的层次结构单独分出来而不是直接把模块划分放到同一个问题里呢?就是要突出其重要性。如果在划分模块时没有把模块的组织方式设计合理,很有可能导致头文件循环引用的情况,这样程序就无法编译。

有一些特殊情况就更需要注意,比如windows中的winsock2.h和windows.h存在冲突,如果先引用windows.h再引用winsock2.h就会出现重定义的情况。如果想使用Win32 Socket API 2,则必须先引用winsock2.h然后引用windows.h,可是如果在多个文件里引用windows,如果顺序控制不好就有可能导致混乱,从而程序无法编译。个人认为最好的方法就是把该引用放入一个头文件,比如叫sysinc.h,然后所有编译单元的头文件都在第一行引用sysinc.h,然后所有的.cpp文件在第一行引用对应的头文件,这样即可保证winsock2.h和windows.h的引用顺序。

4. 命名空间
C语言是不支持命名空间的,以前人们为了避免标识符冲突都是加前缀或者后缀,但这样会使得标识符很长,看起来不舒服,而且写起来很麻烦(虽然现在IDE对智能感知的支持已经使这个变得很方便),还老感觉怪怪的,不够自然。所以,后来的语言都对命名空间(或类似功能)进行了支持,如C++、C#、Java、Python等,所以在大型的C++项目为什么不用这个新的特性呢?

使用不同命名空间中的标识符识不要图省事使用using namespace ns; 这样的语句把整个ns命令空间内的标识符都引进来,这样便失去了命名空间的作用。因为这样做可行是需要一个前提的,就是知道这个不会冲突,可是谁能预知未来不会冲突呢?前些天就遇到在WINDOWS、Linux、AIX三个平台编译都没有问题,可是到了Solaris就出了问题,原因是在系统的头文件if.h中有一个结构定义struct map,这个跟C++标准库中的模块map冲突,导致无法编译。

在这方面,boost的作者们不愧是C++的专家,这方面做得很好,而ACE就不是太好,所有标识符通过前缀ACE_来标识,保必呢,而且是大写,看起来很不舒服,如果要用呢只能忍了,或者自己单独写一个头文件,使用typedef或者用宏定义把标识符都给改了,然后把新定义的标识符封装到一个ace命令空间中,这也是个不错的主意。

5. 代码注释
时间长了谁也难保证记住每一段代码的用途,特别是一些特别技巧,一定要随手写下来,为以后自己维护或与同事合作都是很必要的,不要怕别人看懂自己的代码对自己有威胁,开放一些,只有互相学习才能促进发展,你把代码给了他人,他人也会给你建议帮你进步的,中国前一百年的历史就是固步自封的沉痛教训,国家如此,个人亦如此。

6.版本管理工具
如果几个人同时维护几十万行代码都没有使用版本管理工具的话,那你每天肯定只能有一种感觉,那就是在浪费生命。浪费自己的生命也就算了,如果作为一个领导者不对此做点改变的话,那就是浪费别人的生命,也就是谋财害命。

7. 代码规范
每个人可能都有自己的编码风格,但是同一个项目,应该只有一种风格,否则可能会影响工作,虽然程序本身的性能和功能都没有会有问题,但对于产品的实际开发过程会有影响,因为它会影响到一些有想法的工程师的心情,当然就会影响工作效率,因为他们老感觉那些代码不够规范,总想去修改这些细节,当改过后可能会被另外的工程师改成另一种风格。所以必须有统一的风格,以统一团队每一位成员的行为。

有一个细节,我还是有体会的。项目中一些旧的代码写得很乱,有的一个函数都能写几百行,而且代码块的花括号{ 和}都是另起一行if-else都是在单独的行,这样着满屏都是花括号和if - else,看起来代码很不紧凑,比如:
if( cond 1 )
{
line …
line …
}
else
{
line …
}
改成
if( cond 1 ){
line …
line …
}else{
line …
}

看起来就紧凑多了。
还有一些代码层次很深,一块代码可能都有四层if条件嵌套,看起来是很费劲的,如果在每一次条件判断都进行退出的话,可读性将会有很大的改善,如
if( cond 1 ){
if( cond 2 ){
if( cond 3){
work line
}
}
}
如果改成下面代码,看起来会更舒服些
if( ! cond 1)
return –1;
if ( ! cond 2 )
return –2;
if( !cond 3 )
return –3;
work line

当然写成
if( cond 1 && cond 2 && cond 3 ){
work line
}
更好,但有些时候并没有这么方便。

8. 自动化构建
如果一个大型项目包含几十个小模块,每次都要手工编译依赖项的话,工作效率是低下的,如果能够自动构建必要的模块将可以大大提高工作效率,即使每次能少输入几个字符也是必要的。
比如我们的一个项目需要在windows、Linux、AIX、HPUX、Solaris五个平台上运行,如果每次编译前都执行configure,然后再执行make,也不是很方便,如果公共的部分写到一个Makefile.comm中,然后为每一个平台的平台依赖项分别写一个Makfile.win,Makefile.linux, Makefile.aix,Makefile.hpux, Makefile.solaris,这样每次只需要输入make –f Makefile.os来编译,但这样要输入的要挺多的,可以再建一个Makefile文件,然后自动检测系统平台,然后调用对应的Makefile,以后只需要输入make就行了,再想简单,可以写个小脚本,文件名为m,里面调用make指令,这样每次只需要输入./m即可,如果还嫌输入的字符太多的话,可以把.加到你的PATH变量中,以后直接输入m就可以了。如果再想简单呢?那只能使用特异功能,玩儿心灵感应了,我实在是想不到其它的办法了。
只是个例子而已,可能实际开发过程中需要更多其它的工作,但当你发现你做某件事时不再是第一次,你就可以考虑下次遇到后如何简化了,这也是“程序修炼之道”中提到的DRY(Don’t Repeat Yourself)法则。

9. 自动化测试
对于一个大型项目,每次构建后都进行手动测试的工作量是很大的。如果公司没有研发团队配备测试人员,而且测试部不能给及时测试的话,研发力会将会有大量的人力浪费在测试上,所以构建自己的自动化测试系统是一项至关重要的内容。

暂时谈到这儿吧,想到再记下来,有机会与网友探讨,各位晚安。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: