C++设计开发规范
2.4. 类型(类、结构、接口、枚举、typedef)的命名
1. 引言
1.1. 核心价值
“集大众之智慧,方为大智慧。”
1.2. 参考
1.2.1. 深度探索C++对象模型/(美)Stanley Lippman著;候捷译. 武汉:华中科技大学出版社,2001.5
1.2.2. C++编程规范/ Sutter H,Alexandrescu A.著;刘基城译. 北京:人民邮电出版社,2006.1
1.2.3. .NET设计规范:.NET约定、惯用法与模式/Krzysztof Cwalina,Brad Abrams著;葛子昂译. 北京:人民邮电出版社,2006.7
1.2.4. Effective C++ Second Edition/Scott Meyers著
1.2.5. Effective C#: 50 Specific Ways to Improve Your C#. By Bill Wagner. Addison Wesley Professional. 2004.3
1.2.6. 高质量C++/C编程指南;林锐著.
1.2.7. Writing Clean Code - Microsoft Techniques for Developing Bug-free C Programs./Steve Maguire 著. 姜静波、佟金荣译. 北京:电子工业出版社
1.2.8. 印艺开发部C和C++编程规范;北大方正电子有限公司,印艺研发中心
1.2.9. FitV项目开发规范;北大方正电子有限公司,杨雷鸣/李果锋,2002
1.3. 约定
1.3.1. √ 符号代表“要求”,表示要求遵守的规范。
1.3.2. l 符号代表“推荐”,表示推荐遵守的规范。
1.3.3. × 符号代表“不要”,表示不应该违反的规范。
1.4. 基本术语
1.4.1. 事件 事实上,C++没有事件的概念。我们称那些具有“通知”性质的回调函数
称为事件。事件表示了一些正在发生或已经发生了的动作。
2.命名规范
“站在使用者的角度进行命名”
一致的命名可以让程序更清晰,让代码具有更强的说明性,从而提高程序的可读性和可维护性。本规范中描述了最基本的命名规范,包括如何使用大小写,并为软件设计开发中涉及到的基本概念的命名提供参考规范,如名字空间、类型、成员、参数命名等。
2.1. 大小写约定
常见的大小写约定有两种:PascalCasing和camelCasing。
√ 要求命名区分大小写。
√ 要求在命名类、结构、接口、函数使用PascalCasing。
例如,要使用class ColumnInformation而不是class columnInformation或
class COLUMNINFORMATION。
√ 要求宏/常量/类、结构的公有常量时的命名全部使用大写(推荐:词与词之间使用下划线分隔)。
例如, RAISE_EXCEPTION(exp)
const int MAX_PRIME_NUMBER = 100
class ApplicationInfo
{
public:
static const int PRODUCT_VERSION = 0x01000000;
}
× 不要使用两个或多个仅仅有大小写区别的名字或宏。
√ 要求命名回调函数的类型时候全部使用大写。
例如,使用typedef void (*CLICKINGEVENTHANDLER)()
而不是typedef void (*ClickingEventHandler)()。
√ 要求在命名函数参数、类/结构的成员变量、局部变量时使用camelCasing。
例如,使用long streamPostion而不是long StreamPostion;
使用long long m_streamPostion而不是long m_StreamPostion。
√ 要求在把由两个字母组成的首字母缩写词全部大写(采用PascalCasing风格命名时)/小写(采用camelCasing风格命名时)。
例如,void ProcessIO(std::stream& ioStream)。
void ProcessHtml(std::stream& ioStream)。
√ 要求在把由三个或三个以上字母组成的首字母缩写词全部大写(采用PascalCasing风格命名时)/ 全部小写(采用camelCasing风格命名时)。
例如,xmlTag,XMLReader。
2.2. 通用命名约定
√ 要求命名要有意义,大小写相间,可以让人顾名思义,一目了然。
例外:循环变量i,j,k...的命名
l 推荐不要过度使用匈牙利命名法。
例如,在SDK的接口中尽量不要使用匈牙利命名法以确保这个命名对于Windows和非Windows(如Linux/Unix)程序员都是公平的。
× 不要使用那些没有被普遍接受的缩写。
例如,不要使用SI来表示没有被广泛接受的名词Spread Item,
不要使用GetNthItem,使用GetItem
× 不要在标识符中使用“Ex”而要使用数字来区分相同API的不同版本
例如,使用void ShowMessage2()而不是void ShowMessageEx()来命名
void ShowMessage()的第二个版本。
l 推荐不要使用“Ex”来区分同一版本的表示两个功能相近的API,最好使用一个更加有意义的名字。
例如,
void DoCommand ()
void DoCommandEx()//不好
void DoPluginCommand()//好,意义更加明确
× 不要在使用“Ex”来区分同一/不同版本的类/结构,最好使用一个更加有意义的名字。
例如,
class StreamPosition
{
…}
class StreamPositionEx
{
..}
//不好
class FixedStreamPosition
{
..}
//好
class DynamicStreamPosition
{
..}
//好
2.3. 名字空间的命名
√ 要求全部使用小写来命名名字空间。
例如,graph,dom,v12sys::io,v12sys,v12sys:: text
×不要使用已有的通用的名字来命名名字空间。
例如,std
× 不要使用相同的名字来命名名字空间和位于此名字空间的类型。
例如,不要在graph命名空间里定义类Graph。
2.4. 类型(类、结构、接口、枚举、typedef)的命名
√ 要求使用名词或名词词组来给类型命名。
例外:少数情况可使用形容词(词组)来给类型命名。
例如,
class PageSetting,class Document //好
class VCodeParser //好
class ParseVCode //不好
√ 要求采用PascalCasing大小写风格。
例外:typedef。
l 推荐使用typedef命名基本类型时,全部使用大写
例如,typedef int VINT32
typedef long VOBJID32
√ 要求让接口的名字以字母I开头。
例如,interface ICopyable,interface IApplication,interface IObserverManager。
2.4.1. 泛型类型的命名
√ 要求使用名词或名词词组来给泛型类型命名,采用PascalCasing大小写风格。
例如,
template<typename TInput , typename TOutput >
class Converter //好
√ 要求命名泛型参数使用范式 Txxx,不要使用_Txxx或其他范式。
template<typename TInput , typename TOutput >
class Converter //好
template<typename _TInput , typename _TOutput >
class Converter //不好
√ 要求使描述属性的名字来命名泛型参数。
例外:如果泛型类型只有一个参数,而且类型参数只有一个字母,使用T类命名参数类型。
例如,template<typename T> bool ToString(const T& value)
template<typename TSession> class SessionChannel(const TSession & session)
2.4.2. 枚举类型的命名
√ 要求使用名词或名词词组来给枚举类型命名,采用PascalCasing大小写风格。
例如,enum kFileType,enum kExportType
× 不要给枚举类型的名字添加“Enum“、“Flag”、“Flags”后缀。
例如,不要定义enum kFileTypeEnum,enum kExportTypeFlags
√ 要求为枚举类型的名字使用“k”前缀。
√ 要求枚举类型值的名字=枚举类型的名字+"_"+枚举类型值的真实意义的名字。
例如,
enum kFileType
{
kFileType_None, kFileType_Ansi, kFileType_Unicode
}
例如,long g_s_VisitTimes,unsigned int g_s_CurrentMessage
√ 要求给全局变量的命名加上g_前缀。
2.5. 成员的命名
2.5.1. 函数的命名
√ 要求使用动词或动词词组来命名函数。
例如,Track,ShowMessage,GetItem
√ 要求用于设置程序状态的函数的名称=Set+表示程序状态的名词、名词词组或形容词。
例如,SetDirty,SetChangeState,SetWindowText
√ 要求用于获取程序状态的函数的名称=Get+表示程序状态的名词、名词词组或形容词。
例如,int GetChangeState() const,int GetEnabled() const
l 推荐用于获取程序状态且返回值为布尔值的函数的名称=Is/Can/Has/Contain/Include等+表示程序状态的名词、名词词组或形容词。
例如,
bool Contain(int item) const,
bool HasUnicodeCharacter
bool IsChanged() const
2.5.2. 事件的命名
√ 要求命名事件类型(即回掉函数)的时候全部使用大写。
例如,typedef void (*BUTTONEVENTHANDLER)();
√ 要求使用动词或动词词组来命名事件
例如,
typedef void (*BUTTONEVENTHANDLER)();
BUTTONEVENTHANDLER Clicking;
BUTTONEVENTHANDLER Clicked;
√ 要求使用现在时和过去时来区别正在发生(之前)和已经发生(之后)的事件。
例如,
typedef void (*BUTTONEVENTHANDLER)();
BUTTONEVENTHANDLER OnBeforeClick;//不规范
BUTTONEVENTHANDLER OnAfterClick; //不规范
BUTTONEVENTHANDLER Clicking;//规范
BUTTONEVENTHANDLER Clicked; //规范
2.5.3. 字段的命名
√ 要求使用名词或名词短语来命名字段。
例如,std::string m_phone,long m_areaCode
√ 要求给静态字段的命名加上s_前缀。
例如,long m_s_timeElapsed,long g_s_VisitTimes
√ 要求给私有或受保护的字段加上m_前缀。
例如,long m_s_timeElapsed
× 不要给公共字段加上m_前缀,公共字段的命名要求采用PascalCasing风格。
例如,
class PointF
{
public:
static const PointF Empty;//好
…
}
2.6. 全局变量/函数的命名
2.6.1. 全局变量的命名
√ 要求使用名词或名词短语来命名字段。
√ 要求全局变量的命名要求采用PascalCasing风格。
例如,
long g_s_VisitTime;//好
long g_s_visitTimes;//不好
√ 要求给静态全局变量字段的命名加上s_前缀。
2.6.2. 全局函数的命名
√ 要求给符合成员函数的命名规则。
2.7. 参数的命名
√ 要求命名参数时使用camelCasing大小写风格。
√ 要求使用具有描述性的参数名。
例如,void ChangePhoto(const std::string& photoFileName,
int photoType)
2.8. typedef
√ 要求常见的typedef遵循下表的规则:
3.类型设计规范
“一切以简单为美“
C++中的类型繁多,其中包括(具体)类、基类、接口、结构、枚举、数组等。在此规范中,我们不详细讨论抽象类和接口,因为这两个类型属于一个特殊的逻辑分组,和扩展性有关,我们在扩展性设计规范中进行讨论。
任何的编程语言都可以看成是一个类型系统。在这个类型系统中,每个类型都扮演这各自的职责,各有其意义:
n (具体)类:在遵循某个特定的开发封闭原则的前提下的对行为和属性的封装。使用class声明。
n 基类:在遵循某个特定的开发封闭原则的前提下的对行为和属性的适度封装。使用class声明。
n 接口:抽象了某个/某些特定的行为,为对象提供了一种抽象的分类准则,它应该是基本稳定的。使用interface声明。
n 结构:用于定义小而简单的类型。使用struct声明。
n 枚举:用于定义一小组值,这一小组值代表着一个逻辑分类。如一星期中的每天。
3.1. 选择需要设计的类型
在类型系统中不同的类型适用于不同的用途,因此遵循着不同的规则,代表着不同的意义。
l 推荐使用基类,如果:
a. 实现某个特定的接口(可选)
b. 对于子类,它的确具有抽象意义
c. 它通过与子类共享成员(属性、函数)或/和定义虚函数来实现抽象
d. 它具有/不具有状态
l 推荐使用抽象类,如果:
e. 具有基类的所有特征
f. 不能被实例化(至少具有一个纯虚函数)
l 推荐使用接口,如果:
a. 它代表了一类行为的抽象,这样的一类行为可以作为一种分类规则区别其他不同类的对象
b. 它的定义应该是基本稳定的,否则考虑使用抽象类而不是接口
c. 它定义功能而不是实现功能,不具有任何状态
d. 精心定义的接口只做一件事情
l 推荐使用结构,如果:
a. 它在逻辑上代表一个独立的值,与基本类型(int、double等)相似
b. 它不需要虚函数(包括虚析构函数)
c. 它总是在栈中被实例化,实例比较小而且生命周期比较段或经常被内嵌在其他对象中
l 推荐使用静态类,如果你设计的类具有下面的特征:
a. 它定义的所有的函数都是public static的
b. 它不需要被实例化
c. 它往往被用于定义一些帮助函数或基于简单性的考虑来整合某个特定的功能。
3.2. 通用设计特性
√ 要求区分接口、结构和类:
结构
使用struct关键字声明,用于定义以数据为中心的实体类型,如
struct PageSetting。
接口
使用interface(即struct)关键字声明,用于定义抽象的行为集合的实体类型,
如interface IDrawing。
类
使用class关键字声明,用于定义以行为为中心的实体类型,如
class TokenParser。
× 不要使用private和protected继承,除非万不得已。
例如,
//不好的做法:使用private继承来实现组合
class A{}
class B : private A{}
//好的做法:使用private成员来实现组合
class A{}
class B
{
private:
A m_a;
}
× 不要使用virutal继承,除非万不得已。
例如,
//不好
class Base1{}
class Base21 : virtual public Base1{}
class Base22 : virtual public Base1{}
class Base3 : public Base21,public Base22{}
l 推荐不要过度使用多继承,特别是实现继承。
3.3. 类的设计
√ 要求用小类代替巨类
小类更易于编写,更易于保证正确、测试和使用。而大类承担太多职责,削弱了封装性。
l 推荐用组合代替继承
避免继承带来的重负:继承是C++中第二紧密地耦合关系,仅次于友元关系。软件
工程的原则之一就是减少耦合。在适当的时候,应该使用组合代替继承。
例外:
如果需要改写基类的虚函数;
如果需要访问基类的保护成员;
如果需要控制多态;
如果需要在基类之前构造已使用过的对象,或在基类之后销毁此对象。
× 不要公开内部数据
数据隐藏是强大的抽象方式,也是强大的模块化机制。应该避免将内部数据句柄/ 指针暴露给外部。
例如,
class Component
{
public:
char* GetBuffer()
{
return m_buffer;
}//不好
const char* GetBuffer() const
{
return m_buffer;
}//好
private:
char* m_buffer;
}
3.4. 抽象类的设计
l 推荐不要在抽象类中定义任何数据成员。
√ 要求在抽象类中定义protect而非public/private的构造函数。
例如,
class AddinBase
{
public:
virtual void Authorizing() = 0;
protected:
AddinBase(){}
}
l 推荐使用接口来定义抽象类的行为。
例如,
class IComponent
{
virtual void AddControl() = 0;
virtual void PendingModification() = 0;
}
class Component : public IComponent
{
public:
virtual void AddControl(){…}
virtual void PendingModification() = 0;
}
3.5. 接口的设计
√ 要求一个接口只做一件事情。
× 不要定义接口,如果这个接口定义的功能很不稳定。
× 不要在接口中定义冗余的、存在二义性的pure virtual函数。
例如,
interface ILayout
{
//返回页数,页索引从0开始
virtual int GetActivePage () const = 0;
//返回传递给UI参数的页数,页索引从1开始,返回值= GetActivePage()+1
virtual int GetUI ActivePage () const = 0; //冗余
}
3.6. 结构的设计
× 不要使用C风格的定义方式。
例如,
//不好
typedef struct tagColorSwatch
{
…} ColorSwatch;
//好
struct ColorSwatch
{
…};
√ 要求为结构提供一个默认的构造函数。
√ 要求为拷贝构造函数设为私有/保护成员,如果不需要拷贝构造函数。
例如,
struct ExportConfiguration
{
protected:
ExportConfiguration(ExportConfiguration& other){}
};
√ 要求为结构提供拷贝构造函数,如果:默认的拷贝构造函数的行为不是所需要的。
√ 要求为结构重载operator=,如果:默认的operator=行为不是所需要的。
√ 要求为结构重载operator==,operator!=,如果:默认的operator==,operator!=行为不是所需要的。
例如,
struct TextProperties
{
const wchar_t* FontName;
bool Bold;
bool Italic;
bool operator==(TextProperties& other)
{
return (Bold == other.Bold) &&
(Italic == other.Italic) &&
(strcmp(FontName, other. FontName) == 0) );
}
};
× 不要设计面面俱到、非常灵活的结构。
例如,
//不好
PageCombineMergeSetting
{
const wchar_t* SourceFilename;
int SourcePage;
bool IsMerge;
bool IsCombine;
Graph::CdRect TargetRectangle; //当IsMerge==true时有效
Graph::CdRect TargetPage; //当IsCombine==true时有效
}
//好,改写为两个结构
struct MergeSetting
{
const wchar_t* SourceFilename;
int SourcePage;
Graph::CdRect TargetRectangle;
}
struct CombineSetting
{
const wchar_t* SourceFilename;
int SourcePage;
Graph::CdRect TargetPage;
}
l 推荐结构中元素的个数应适中。若结构中元素个数过多可考虑依据某种原则把元素组成不同的子结构,以减少原结构中元素的个数。
l 推荐仔细设计结构中元素的布局与排列顺序,使结构容易理解、节省占用空间,并减少引起误用现象。
l 推荐使用__declspec(align(x))方式定义结构的字节对齐方式:
例如,
//定义一个8字节对齐的结构
__declspec(align(8))
struct A
{
double a,
int b;
}
3.7. 枚举的设计
× 不要使用C风格的定义方式。
例如,
//不好
typedef enum tagCOLORSWATCHTYPE
{
…} COLORSWATCHTYPE;
//好
enum kColorSwatchType
{
…};
√ 要求优先使用枚举而不要使用静态常量或宏定义。
例如,
//不好
struct ApplicationInfo
{
static const int UnknownProduct = 0;
static const int BusinessProduct = 1;
static const int NewsProduct = 2;
…};
#define FreeProduct 3
//好
enum kProductType
{
kProductType_Unknown,
kProductType_Business,
kProductType_News,
kProductType_Free
}
× 不要把枚举用于开放的集合。
例如,操作系统的版本,朋友的名字等。
× 不要把sentinel值包含在枚举值中。
例如,
//好
enum kDeskType
{
kDeskType_Unknown = 0,
kDeskType_Circular = 1,
kProductType_Rectangular = 2,
kProductType_LastValue = 2
//不好,不需要定义这个枚举值
}
√ 要求为简单枚举类型提供零值。
例如,
enum kCompressionType
{
kCompressionType_None
kCompressionType_GZip,
kCompressionType_Deflate
}
enum kRequestType
{
kRequestType_Error, kRequestType_Warning, kRequestType_Information
}
√ 要求使用复数名词/名词短语来命名标记枚举。
例如,
enum kFileShareModes
{
kFileShareModes_Read = 1,
kFileShareModes_Write = 2,
kFileShareModes_ReadWrite = kFileShareModes_Read | kFileShareModes_Write,
}
4.成员设计规范
4.1. 一般规范
√ 要求重载成员之间的相同参数的顺序和名称要一致。
例如,
class EventLog
{
public:
EventLog();
EventLog(const string& logName);
EventLog(const string& logName, const string& machineName);
}
× 不要在重载中随意地给参数命名。
√ 要求在成员方法的签名添加”const”,如果成员方法不需要改变对象的状态。
例如,
class DocumentEnvironment
{
public:
const std::string& GetAuthor()
{
…}//不好
const std::string& GetAuthor() const
{ …}//好
}
× 不要对参数为指针和数字类型的函数进行重载。
例如,
class StockHolder
{
public:
void SearchStock(int stockCode);
void SearchStock(const wchar_t* stockName);
}
//如果按照下面的方式调用
StockHolder stockholder;
Stockholder. SearchStock(0);//调用存在二义性
√ 要求优先使用重载,而不是定义具有默认参数的成员。
例如,
class Point
{
public:
void Move(int x, int y = 0)
{
…}//不好
//好
void Move(int x)
{ …}
void Move(int x,int y)
{ …}
}
√ 要求优先使用重载,而不是定义具有默认参数的成员。
√ 要求对短小的函数合理使用inline。
× 不要定义inline virtual的成员函数。
例如,
class Point
{
public:
inline virtual void Move(int x, int y = 0)
{
…}//不好
void Move(int x,int y=0)
{ …}//好
virtual void Move(int x,int y=0)
{ …}//好
}
4.2. 构造/析构函数的设计
√ 要求在构造函数中作最少的事情且不要在构造函数中抛出异常。
l 推荐避免在构造函数中出现隐式赋值。
例如,
class A
{
public:
A(int a){..} //不好
explicit A(int a){..} //好
}
√ 要求重新编写/禁用拷贝构造函数和operator=重载
如果编译器生成的拷贝构造函数和operator=重载不是所期望的:如果需要这两个操作,重新编写这两个函数;否则,显示地禁用它们。
例如,
//显示地禁用拷贝构造函数和operator=重载
class Envidence
{
private:
Envidence(const Envidence& other)
{
…}
Envidence& operator=(const Envidence& other)
{ …}
}
√ 要求使将基类析构函数设为public且virtual,或设为protect而非virtual。
如果允许通过指向基类Base的指针执行删除操作,那么将基类析构函数设为public且virtual,或设为protect而非virtual。
例如,
class AuthrizationStrategy
{
public:
AuthrizationStrategy();
virtual AuthrizationStrategy();
};
class LogonSession
{
public:
LogonSession();
protected:
LogonSession();
};
4.3. 操作符重载
√ 要求以对称的方式重载操作符
例如,如果重载了operator==,那么应该同时重载operator!=。同样,如果重载了operator<,那么应该同时重载operator>。
× 不要提供类型转换操作符,如果没有明确的需求。
× 不要提供隐式类型转换操作符,如果该类型转换可能会丢失精度。
例如,由于double的覆盖范围比int要广,因此不应该存在把double隐式地转换为int的操作符。即使类型转换可能会丢失精度,也可以提供显式的类型转换操作符。
4.4. 字段的设计
l 推荐在结构中提供public字段(默认)
例如,
struct PageSetting
{
int PageCount;
int LeftMargin;
…
}
× 不要在类中不要提供public/protected字段
例如,
class ChangeManager
{
public:
ChangeManager(int changeType, int changeEvent)
: m_changeType(changeType),
m_changeEvent (changeEvent),
{ …}
int GetChangeType() const
{
return m_changeType;
}
int GetChangeEvent() const
{
return m_changeEvent;
}
private:
int m_changeType;
int m_changeEvent;
}
4.5. 参数的设计
× 不要使用保留参数。
例如,
//不好
void Method(SomeOption option,object reserved)
//不好
void Method(SomeOption option)
l 推荐将所有的引用/返回参数放到函数签名的后面,这样可以使函数签 名更易于理解。
例如,
bool GetObjectFromID(VOBJID32 oidObject,vtl::Cv_PTR& objReturned);
void CreateNullSpreadItem(VCLSID clsidProxy,VOBJID32& oidNewSpreadItem);
√ 要求在重载函数或实现接口函数时保持参数命名的一致性。
例如,
//重载时
bool Equals(const string& value);
bool Equals(const string& value, bool ignoreCase); //好
bool Equals(const string& other, bool ignoreCase); //不好
//实现接口时
public ISpreadItem
{
virtual bool Transform(const Matrix& matrix) = 0;
}
classs SpreadItem : public ISpreadItem
{
public :
bool Transform(const Matrix& matrix); //好
bool Transform(const Matrix& trasformMatrix); //不好
}
l 推荐不要使用不定长的参数。
例如,
class StringUtil
{
string Format(const wchar_t* format,…);//不推荐
}
× 不要使参数长度超过一定的数量(7个以内)。
√ 要求对于不会被改变的简单类型的参数使用值传递,而不是使用引用传递。
例如,
void ShowMessage(const std::string& message, int option);//好
void ShowMessage(const std::string& message, const int& option);//不好
void ShowMessage(const std::string& message, const int option);//不好
√ 要求对于不会被改变的复合类型的参数使用引用传递,而不是使用值传递。
例如,
void Log(const std::string& message,const Color& backgroundColor);//好
void Log(std::string message, Color backgroundColor);//不好
4.6. 返回值的设计
× 不要返回字段的非const引用,除非完全必要。
例如,
class PhoneBook
{
public:
Address& GetAddress() const
{
return m_address;
}//不好
const Address& GetAddress() const
{
return m_address;
}//好
private:
Address m_address;
}
√ 要求返回字段的const引用。
Class Document
{
public:
const DocumentInfo& GetAddress() const
{ return m_documentInfo;} //好
const string& GetAddress() const
{ return m_fileName;}//好
//或
const wchar_t* GetAddress() const
{ return m_fileName;}//好
private:
DocumentInfo m_documentInfo;
string m_fileName;
}
4.7. 全局变量/函数/常量/宏的设计
× 不要使用全局变量和函数,将全局变量和函数作为某个类的静态成员函数。
例如,
//不好
extern void ToCMYKColor(Color& color);
extern void ToRGBColor(Color& color);
//好
class ColorConverter
{
public:
static void ToCMYKColor(Color& color);
static void ToRGBColor(Color& color);
}
× 不要使用”Magic Number”,使用const修饰符来进行定义。
× 不要使用#define来定义常量,应该使用const修饰符或采用enum来定义常量。
例如,
//不好
#define FITUNKNOWNBLOCK 0
#define FITPICTUREBLOCK 1
#define FITTEXTBLOCK 2
//好
enum kFitBlockType
{
kFitBlockType_Unknown, kFitBlockType_Picture, kFitBlockType_Text
}
5.扩展性设计规范
扩展的方式有很多种,常见的有基类/抽象类继承、接口实现、回调函数、虚函数重载、组合等。
l 推荐(相对)遵守敏捷开发(面向对象设计)基本原则:
a. SRP(单一职责原则)
就一个类而言,应该有仅只有一个引起它变化的原因。
b. OCP(开放封闭原则)
软件实体(类、模块、函数等)应该是可扩展的,但是不可修改
c. LSP(Liskov替换原则)
子类型必须能够替换掉它的基类型。
d. DIP(依赖倒置原则)
抽象不应该依赖于细节。细节应该依赖于抽象。
e. ISP(接口隔离原则)
不应该强迫客户依赖于他们不用的方法。接口属于客户,而不应该属于它们的类层次。
f. REP(重用发布等价原则)
重用的粒度就是发布的粒度。
g. CCP(共同封闭原则)
包中的所有类对于同一性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包的所有类产生影响,而对于其他的包则不产生影响。
h. CRP(共同重用原则)
一个包中的所有类应该是共同重用的。如果重用了包中的一个类,那么就要重用包中的所有类。
i. ADP(无环依赖原则)
在包的依赖关系图中不允许存在环。
j. SDP(稳定依赖原则)
朝着稳定的方向的进行依赖。
k. SAP(稳定抽象原则)
包的抽象程度应该和其稳定程度一致。
√ 要求在命名基类时,选择使用/不使用”Base”后缀,但不允许使用其它后缀。
class SpreadItemBase{…}//好
class SpreadItem{…}//好
class SpreadItemRoot{…}//不好
class SpreadItemTop{…}//不好
√ 要求使用public继承。
× 不要为派生子类提供可访问保护字段(除非别无选择)。
例如,
class EventHandler
{
protected:
object m_virtualView;//不好,会造成强耦合
}
× 不要使用友元类、友元函数(除非别无选择)
× 不要让一个类去实现与类的责任无关的接口,否则会造成类膨胀
例如,
interface IExportService
{ …}
interface ILayoutService
{ …}
interface ITypesetService
{ …}
//不好
class LayoutService: public IExport,
public ILayoutService,
public ITypesetService
{
…}
//推荐解决方案
class LayoutService: public ILayoutService
{
…}
class ExportService: public IExport
{
…}
class TypesetService: public ITypesetService
{
…}
× 不要使用接口,如果这个接口极不稳定
l 推荐使用回调函数来执行被依赖的实体(类、模块等) ,
相对虚函数重载和继承,这种扩展方式更灵活。但这种扩展方式的性能略微低于前两者。
例如,
class Layer
{
public:
typedef void (*LAYEREVENTHANDLER)(VOBJID32 oidSpreadItem);
///
//public event
LAYEREVENTHANDLER SpreadItemDeleting;
LAYEREVENTHANDLER SpreadItemDeleted;
public:
void DeleteSpreadItem(VOBJID32 oidSpreadItem)
{
…
if (SpreadItemDeleting)
SpreadItemDeleting(oidSpreadItem);
…
if (SpreadItemDeleted)
SpreadItemDeleted(oidSpreadItem);
}
}
6.移植性设计规范
本规范中只讨论C++应用程序在不同的操作系统(如Linux和Windos操作系统)平台之间的移植性。
l 推荐不要加入移植性设计,如果需求/软件架构没有明确要支持可移植性。
l 推荐尽量使用C标准库函数。
√ 要求分离出不可移植的代码。
例如,
n 汇编代码
#ifdef SOMECODE __asm{…}
n 文件分隔符
WINDOWS平台采用的是”/”,而UNIX/linux使用的是”/”。
#ifdef UNIX
#define SEPERATOR ‘/’
#endif
#ifdef _WINDOWS||__MSDOS__
#define SEPERATOR ‘//’
#endif
n 基本类型,不同操作系统版本对于基本类型、指针的定义是有差别的。
如在64位系统上,指针是64位的。;而在32位系统上,指针是32位的。
typedef char int8_t;
typedef __int16 int16_t;
typedef __int32 int32_t;
typedef __int64 int64_t;
typedef unsigned char uint8_t;
typedef unsigned __int16 uint16_t;
typedef unsigned int uint_t;
typedef unsigned __int32 uint32_t;
typedef unsigned __int64 uint64_t;
#ifndef _INTPTR_T_DEFINED
#ifdef _WIN64
typedef __int64 intptr_t;
#else
typedef int intptr_t;
#endif
#define _INTPTR_T_DEFINED
#endif
#ifndef _UINTPTR_T_DEFINED
#ifdef _WIN64
typedef unsigned __int64 uintptr_t;
#else
typedef unsigned int uintptr_t;
#endif
#define _UINTPTR_T_DEFINED
#endif
typedef intptr_t ssize_t;
√ 要求抽象应用程序依赖于平台API相关的接口。
例如,对于线程的操作,linux和windows平台的API有着不同的定义。
那么我们可以把线程操作相关的接口给抽象出来,如:
class Thread
{
public:
static Thread* Create();
static Thread* CurrentThread() const;
void Suspend();
void Join();
void Sleep();
void Start();
uint_t GetID() const;
bool IsAlive() const;
void SetPriority(int32_t priority);
int32_t GetPriority() const;
//...
};
× 不要使用平台相关的类型或typedef,平台中立的类型或typedef。
例如,
typedef VDWORD unsigned long;
typedef VWORD unsigned short;
DWORD value; //不好
VDWORD value//好
× 不要使用移位操作符来进行指针运算。
× 不要假设在不同的平台上相同的结构(struct)具有同样的数据长度、对齐方式和字段的存放顺序。
例如,
struct StreamPosition
{
int __Size;// 不要使用__Size来记录结构长度。
int BeginPosition;
int EndPosition;
};
//不好
StreamPosition streamPos;
int begPos = *(int*) (&streamPos);