Chinaunix首页 | 论坛 | 博客
  • 博客访问: 368512
  • 博文数量: 68
  • 博客积分: 5157
  • 博客等级: 大校
  • 技术积分: 1560
  • 用 户 组: 普通用户
  • 注册时间: 2006-04-20 10:05
文章分类

全部博文(68)

文章存档

2013年(1)

2012年(2)

2011年(11)

2010年(9)

2009年(22)

2008年(23)

我的朋友

分类: C/C++

2009-12-15 17:21:12

                              C++编程规范之编程风格
 

设计风格
第5条:一个实体应该只有一个紧凑的职责
摘要:一次只解决一个问题:只给一个实体(变量、类、函数、名字空间、模块和库)赋予一个定义良好的职责。随着实体变大,其职责范围自然也会扩大,但是职责不应该发散。
       对于string的抱怨见于Sutter先生的《Exceptional C++ Style》,其中将string做了完全的解构,非常精彩。
       事物往往是由小变大的发展。但是,到了一定的程度就需要有一些措施,比如公司大了,就需要改变管理的架构;程序库大了就需要重构。这样的过程一定是痛苦的,但如果为了避免短痛而无限制的拖下去,这个摊子就烂掉了。
 
第6条:正确、简单和清晰第一
摘要:软件简单为美:正确优于速度、简单优于复杂、清晰优于机巧。
       避免使用程序设计语言中的冷僻特性。应该使用最简单的有效技术。对这一点我还算比较了解。我已经学会了很多的技巧,但通常在代码设计中,我尽量避免使用技巧,最简单平实的代码才是最好的代码。很多的技巧容易导致代码可读性变得很差。
 
第7条:编程中应该知道何时和如何考虑可伸缩性
摘要:小心数据的爆炸性增长。
       对我来说,我设计的原则通常原因拿空间来换时间,好在存储的增长很快,没有在这一方面给我带来麻烦,值得我警醒的是我对程序的可伸缩性考虑还不够。
 
第8条:不要进行不成熟的优化
摘要:拉丁谚语云,快马无需鞭策。优化的第一原则是不要优化,第二原则是还是不要优化。再三测试,而后优化。
       对于大的系统来说,优化并不是一件简单的事情,需要考虑的因素很多,CPU、内存、磁盘I\O、网络状况……,在这之间的复杂关系没有梳理清楚之前,进行优化,是一件很不安全的事情。事实上,优化过后有大量的程序需要测试需要做。未必优化完成后,这些后续工作可以跟的上,所以在进行优化之前还是需要多多慎重。
 
第9条:不要进行不成熟的劣化
摘要:放松自己,轻松编程。
       在可以使用标准库的情况下,优先使用标准库。我想自己实现的算法,并不能比世界上最优秀的专家强,并且写出清晰的,可读性很强的代码,对程序是非常有帮助的。
 
第10条:尽量减少全局和共享数据
摘要:共享数据会导致冲突;避免共享数据,尤其是全局数据,共享数据会增加耦合度,从而降低可维护性,通常还会导致降低性能。
       对此条,我深表赞同,过多的成员变量往往使程序变得非常难以阅读,这还会带来多线程同步的问题,而且使程序之间的耦合变得非常夸张。
       记得VC6的string么?引用计数带来的好处远远不足以抵偿同步带来的效率损失。
 
第11条:隐藏信息
摘要:不要泄密:不要公开提供抽象的实体的内部信息。
       尽量以接口的方式提供数据,这样的好处是如果对数据操作的要求有所改变的时候,只需要很少的修改。我基本可以做到将数据隐藏,但对于线程安全的容器,现在的相关工作还比较少,前几天出现的问题,给了我一个教训——即使是线程安全的容器,也难保哪天需要更换容器类型。
 
第12条:懂得何时和如何进行并发性编程
摘要:Thread safely

       C++目前的标准并没有加上线程的概念,也就是说,所有的线程相关的工作都是平台相关的,我们希望下一代C++标准中出现线程的身影,虽然说现在ACE之类的扩展开源库也够用了。
如果需要线程安全,需要了解平台的特性,了解线程的操作和同步化原语。使用C++对平台相关性进行抽象,这部分的工作量相当大,但很多开源库已经做了,推荐使用ACE。保证非共享对象的独立,在多线程程序中,我觉得引用计数是一项双刃剑,需要酌情处理,例如VC6的std::string。加锁的时候,需要注意锁的释放,特别是中途返回和异常退出。加锁的时候需要考量加锁的策略。内部加锁将工作放在了调用者一端,调用者需要对共享对象比较了解,而适当的加以串行化。外部加锁,将锁封装在内部,对外提供线程安全的接口,例如线程安全的容器。在内部加锁的过程中,需要考虑加锁的范围、粒度和线程安全性。对操作的相关性要特别注意。对于大多数数据,应该是不加锁的,特别是一些从来不会修改的数据和从来不会跨线程操作的数据。
在进行加锁的同时,不可忽略的是加锁所带来的消耗。如果一旦线程安全的代价过大,就需要考虑修改设计了。
 
第13条:确保资源为对象所拥有。使用显式的RAII和智能指针
摘要:如果有能力操纵利器,没有道理不用。RAII(resource acquisition is intialization)
       有什么可说呢?参考ACE的ACE_Guard的加锁惯用法。构造函数和析构函数是Stroustrup给我们的礼物。

编程风格
第14条:宁要编译时和连接时错误,也不要运行时错误
摘要:能够在编译时做的事情,就不要推迟到运行时。
       我记得在《重构》中有一条叫做“用测试取代异常”,我觉得差不多表达的是一个意思,即运行期错误的代价比较大,所以尽量转化成编译期错误和链接错误。这和人生的道理是相通的,我一直坚持认为,如果一定要摔跟头的话,越早摔越好。因为摔得越晚,成本越高。但同时还有一个问题就是,千万不要摔趴下起不来了。人生是很漫长的历程,要比拼的是韧劲。
 
第15条:积极使用const
摘要:const是我们的朋友,所以要设计的时候,const应该是默认选项,常量很安全,很省心。
       使用const可以避免很多无意识的行为,当把一些成员从非const转变成const,也许你会吓一跳。
 
第16条:避免使用宏
摘要:宏是非常可怕的工具,他的攻击性一定超过了常人的想象。
       宏的可怕之处在于它非常不可控,你不知道它能做些什么。如果你用过ACE就知道了,在ACE之下,main函数就不再是main函数了。Sorry,我无意模仿唐僧。但是能不用宏还是不要用宏吧。
       宏也有好的用处,比如编译宏以及断言。
 
第17条:避免使用Magic Number
摘要:避免使用一些数字常量或者变量名,这常常带来混淆。
       这是初学者喜欢的,我花了很大力气戒除。
 
第18条:尽可能局部地声明变量
摘要:避免作用域膨胀,对于变量来说,应该是生命域越短越好。
       这样做明显的好处是可以避免名字空间的污染。另外说一点额外的话,函数中空间是在编译器就已经决定的。C++只有在函数的压栈和清栈过程中才涉及到栈的空间。这是C++非常强大的静态数据模型,他能保持非常稳定的运行。那么在同一个函数内,也存在不同的生命域,但这些变量的分配在编译时就已经决定,即使存在重名,即使生命域并不是整个函数范围,它都能独享一块空间,直到函数结束。
       当然存在特例就是循环的相同计算外提。
 
第19条:总是初始化变量
摘要:一切从头开始,养成定义就初始化变量的习惯。
       不想多说什么,我曾经写过一篇Blog——《错误发生原因之首》。看来万事都是殊途同归的。
 
第20条:避免函数过长,避免嵌套过深
摘要:短胜于长,平胜于优
       这一问题在先前的《重构》中以及提及,因为过长和嵌套过多的函数是重构的重点关注对象,并且给出了各种解决办法。
 
第21条:避免跨编译单元的初始化依赖
摘要:保持初始化的顺序:不同编译单元中的名字空间级对象决不应该在初始化上互相依赖,因为谁都不知道初始化是什么样子的。
全局变量总是越少越好,而且全局变量之间的初始化不应该相互依赖。
 
第22条:尽量减少定义性依赖。避免循环依赖
摘要:不要过分依赖,不要互相依赖。
       在我不是很熟悉C++的岁月里,我曾经被循环依赖深深的困扰,说起来是岁月,其实也就是不到一年前,其实有些东西可能看上去很困难,但实际上深入进去并非是那么复杂,所以不要被眼前的困难吓倒。
       可能从设计的角度出发,这种依赖无法避免,而这也不一定全都是坏事,关键是要将这些依赖约束到相同的模块中,避免造成这种约束的扩散。
 
第23条:头文件应该自给自足
摘要:各司其职。
在头文件的编写中,需要考虑到这一头文件还需要依赖哪些资源,将这些需要引用的头文件包含在此头文件中,而不应该再由用户来确定,包含该头文件后还需要包含什么其他的头文件。可以在编译的时候,独立编译每一个头文件,确认是否可以通过编译。
 
第24条:总是编写内部#include保护符,决不要编写外部的#include保护符
摘要:为头文件添加保护
       如果你使用的IDE是VC6,似乎这并不是一个问题,当你用VC没生成一个类的时候,在头文件中总有类似于
#if !defined(AFX_SS7ADAPTERTHREAD_H__3D06B248_9C32_41E4_9425_07AED625454F__INCLUDED_)
#define AFX_SS7ADAPTERTHREAD_H__3D06B248_9C32_41E4_9425_07AED625454F__INCLUDED_
……
#endif // !defined(AFX_SS7ADAPTERTHREAD_H__3D06B248_9C32_41E4_9425_07AED625454F__INCLUDED_)

       引用这些不是为了增加空间,然而定义这么复杂的include guard,Microsoft一定有它的道理。这样的宏可以保证唯一性,而唯一性就是为了避免头文件的重复包含。include guard应该放在头文件内部,就像微软做的一样。很多人恨它,但它的存在一定有它的道理。

阅读(714) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~