概述
撰写编码规范可以清晰定义如下条款 有规可循才是真正的规范
原则: 编程时必须坚持的指导思想
规则: 编程时强制必须遵守的约定
建议: 编程时必须加以考虑的约定
说明: 对 原则
规则
建议
进行必要的解释
示例: 对 原则
规则
建议
从 正/反 两个方面给出例子
命名规范
禁止一切缩写词 即使是 频繁的
常见的
全局变量
以g_
开头标识
常量
以你当前所划分的库名和c_
开头标识
变量
禁止大写 缩写 混乱的命名 示例:
void aaa() {
int VarValue = 123;
int VAR_value = 123;
int VARIABLEVALUE = 123;
int varvalue = 123;
}
所有变量都小写加下划线 不允许使用缩写词
void aaa() {
int variable_value = 123;
}
变量指针的定义 *
应该紧跟着类型 在变量末尾加 _p
标识 示例:
void aaa() {
int* variable_p = nullptr;
int& variable = variable_p;
}
引用变量的定义 示例:
void aaa() {
int a = 123;
int& variable = a;
}
类
禁止忽略访问权限声明
以当前所划分的库名和规范命名组合 示例:
class assSocketServer {
}
基类的定义 示例:
class assSocketServer : public assSocketBase {
}
类成员
禁止不加权限声明直接声明成员变量 示例:
class aaa {
int variable_value;
char *scr;
}
所有成员变量都大写开头并且以m_
开头标识 示例:
class aaa {
private:
int m_VariableValue;
protected:
char* m_String;
}
访问控制块的顺序
禁止恶意调整顺序
class aaa {
public:
private:
protected:
}
构造 析构 成员函数和成员变量
如果你的成员变量超过一个应该被 //(* 数据
标识头包裹
类的构造 析构 成员函数顺序参考 示例:
class astSize {
//(* 数据
private:
int m_Width,
m_Height;
//*)
public:
astSize();
astSize( int Width, int Height );
~astSize();
astSize operator * ( int Value ) const;
void SetWidth( int Width ); // 设置宽度
int GetWidth() const; // 获取宽度
}
初始值列表
函数
重载
禁止无意义的重载 参考如下愚蠢的代码:
typedef int Identifier;
typedef int ClassNum;
typedef int Age;
void Register( Identifier Value, ClassNum Value, Age Value );
void Register( ClassNum Value, Identifier Value, Age Value );
void Register( Age Value, ClassNum Value, Identifier Value );
引用参数
禁止丢弃 const
修饰符 凡是引用参数都视为输入 若想表达输出请用指针 示例:
void Read( const astString& FileName, char* ReturnBuffer );
函数指针
using CompareFunctionString = int ( * )( astString Item1, astString Item2 );
结构体
枚举
命名空间
匿名空间
文件名
头文件的引入顺序
在头文件中:
- 包含当前工程中所需要的自定义头文件(顺序自定)
- 包含第三方程序库的头文件
- 包含标准头文件
在源文件中:
- 包含该源文件对应的头文件(如果存在)
- 包含当前工程所需要的自定义头文件
- 包含第三方程序库的头文件
- 包含标准头文件
避免运行时错误
所有指针获取时必须判断是否为空
循环体内禁止远程调用
单用户系统设计问题不是太大 如果准备上线给多用户使用会造成超级并发 对服务器这可是个灾难打击
禁止循环体内创建对象
并发时一致性
调用第三方服务必须有超时处理函数
禁止远程调用服务时没有超时处理 为了稳定性 防止被上游服务超时造成程序假死
接口幂等性
多次发出同一个请求 必须保证操作只执行一次
多线程异步使用线程池时作隔离
禁止提前创建对象
释放无用对象
合理的使用变量
将你的变量限制在合理的范围内 而不是都使用局部变量
错误示例:
void aaa() {
int i= GetCount();
if( i > 0 );
int bbb= ccc();
if( bbb != 0 );
}
正确示例:
void aaa() {
if( int i= GetCount(); i > 0 );
if( int bbb= ccc()); bbb != 0 );
}
变量的重复计算
循环中应该避免被反复计算的变量
循环中第二个条件判断语句每次循环都会重复调用函数
错误示例:
for( int i=0; i < GetCount(); ++i );
正确示例:
for( int i=0, count = GetCount(); i < count; ++i );
当有多个数据需要处理时
示例:
int count = GetCount();
for( int i=0; i < count; ++i );
for( int j=0; j < count; ++j );
禁止魔法数字
错误示例:
if( a == -1 ){
return 0;
}
正确示例:
namespace assConstValue {
enum {
Invalid = -1, //!< 无意义的值
None, //!< 无效值
MaximumValue = 2'147'483'640 //!< 限制全局最大值
};
};
if( a == assConstValue::Invalid ){
return assConstValue::None;
}
参数入栈检查
带着不合法的参数执行一段未知代码带来的风险是巨大的
不合法的参数输出( 例如: 返回值 异常等) : 显然这是必须检查 不然函数本身的逻辑是错误的
未定义的参数输出: 什么都不干是上策 因为你既无权增加输出的含义 也无权增加抛出的异常
必须保证不管别人传什么参数进来你自己不出事故 处理不了就丢弃参数返回 谨慎考虑抛出异常
程序的健壮性比微不足道的性能重要太多了
禁止无用注释
示例:
int* integer; // pointer to integer
慎用静态变量
慎用模板
慎用auto
禁止include里出现相对路径
禁止函数的参数使用小写单词
自描述的变量名和方法名比你写一堆注释更有价值
示例:
class assColour {
assColour( unsigned char Red, unsigned char Green, unsigned char Blue ) {
...
}
禁止头文件有实现的代码
你可能觉得这样做程序会非常快 很遗憾这样又带来了巨大的维护代价
若代码变动所有依赖这个库的程序都要重新编译
禁止define来定义类型
慎用函数return 尽量只有一个出口
禁止在数组里使用魔法数访问 只能用方括号的形式访问
慎用比较条件 比较的时候左边要放常量防止错误的赋值
慎用goto语句
简短的函数使用这个语句可以提高效率和结构清晰
示例:
void fun( int a ) {
if( a == b ) {
...
goto exit;
}
exit:
... // 清理资源
}
当函数处理 多个条件
多个goto语句
代码行数过多
这会导致结构混乱了这样使用是禁止的
示例:
void fun( int a ) {
conduct:
if( a == b ) {
...
goto exit;
}
...
if( a == c ) {
...
goto conduct;
}
...
exit:
... // 清理资源
}
当函数处理 多个条件
代码行数过多
这会导致结构逻辑不清晰 可能会忽略一些条件导致提前退出
如果确实需要处理多个条件可以参考如下改进代码
示例:
void fun( int a ) {
if( a == b ) {
...
goto exit;
}
if( a == c ) {
...
goto exit;
}
...
exit:
... // 清理资源
}
改进代码:
void fun( int a ) {
do {
if( a == b ) {
...
break;
}
if( a == c ) {
...
break;
}
...
} while( 0 );
//exit:
... // 清理资源
}
慎用三元运算符
参考如下代码是否过于复杂 如果我不说你肯定没发现写漏了一个条件 程序运行中存在崩溃风险
局部变量在短小的函数内没必要const修饰
示例:
FcitxIM* im = FcitxInstanceGetCurrentIM( ipc->owner );
const char* name = ( im &&
im->strName &&
fcitx_utf8_check_string( im->strName ) ) ? im->strName : "";
const char* uniqueName = ( im &&
im->uniqueName &&
fcitx_utf8_check_string( im->uniqueName ) ) ? im->uniqueName : "";
const char* langCode = ( im &&
fcitx_utf8_check_string( im->langCode ) ) ? im->langCode : "";
我更喜欢用这种层次分明的代码 至少可以少出错 再次慎重提醒只要是指针必须先检查在使用
改进代码:
FcitxIM* im = FcitxInstanceGetCurrentIM( ipc->owner );
const char* name = "";
const char* uniqueName = "";
const char* langCode = "";
if( im ) {
if( im->strName &&
fcitx_utf8_check_string( im->strName ) ) {
name = im->strName;
}
if( im->uniqueName &&
fcitx_utf8_check_string( im->uniqueName ) ) {
uniqueName = im->uniqueName;
}
if( im->langCode &&
fcitx_utf8_check_string( im->langCode ) ) {
langCode = im->langCode;
}
}
条件语句
多个条件判断时应当加括号
尝试解释如下条件如何执行的
错误示例:
if( retVal == IRV_TO_PROCESS &&
input->keyReleased == keyHandle[count].kr &&
have_hk1 ||
have_hk2 ) {
...
}
不应当优先级高可以省略括号
改进代码:
if( ( retVal == IRV_TO_PROCESS ) &&
( input->keyReleased == keyHandle[count].kr ) &&
( have_hk1 ||
have_hk2 ) ) {
...
}
单词规范化
检查参数类的函数方法(is/has/have)
is
主要强调当前运行时的检查 具体点就是这个变量可能随时变化 例如
has
主要强调当前完成时的检查 具体点就是这个变量已经完成了初始化不会在发生变化 例如
have
主要强调当前的变量整体是否有价值 例如
is
示例:
class assColour {
assColour( unsigned char Red ) {
m_Red = Red;
}
void SetRed( unsigned char Red ) {
m_Red = Red;
}
bool IsRed() const {
return m_Red != 0;
}
};
assColour a( 0 );
if( a.IsRed() ) { // 检查实例化对象的值是否有效
a.SetRed( 55 );
}
has
示例:
class assColour {
assColour() {
m_Red = 22;
}
bool HasRed() const {
return m_Red != 0;
}
};
assColour a;
if( a.HasRed() ) { // 检查实例化对象的值是期望的值没有被外部所修改
...
}
have
示例:
class assColour {
assColour( unsigned char Red, unsigned char Green, unsigned char Blue ) {
m_Red = Red;
m_Green = Green;
m_Blue = Blue;
}
bool HaveRGB() const {
return ( m_Red != 0 ) &&
( m_Green != 0 ) &&
( m_Blue != 0 );
}
};
assColour a( 11, 0, 33 );
if( a.HaveRGB() ){ // 检查实例化对象的所有值都是有意义的
...
}
Callback
回调函数使用
空数据的表达方式(Invalid/None)
Invalid
Invalid = -1;
None
None = 0;
数据的起始地址的表达方式(Index/Position)
Index
Index = 0;
Position
Position = 1;
字符块的数据长度的表达方式(Length/Count)
Length
用于表达字符串不包含结尾符号( \0
) 和实际数据的长度永远相差 1字节
或 4字节
Count
用于表达字符串包含结尾符号( \0
) 和实际数据的长度永远相匹配
| a 用于通用占位符 | s 用于通用标准库 | e 用于枚举类型 |
| w 用于公开符号 | k 用于通用扩展层 | t 用于变量类型 |
| x 用于内部符号 | n 用于通用命名空间 | c 用于常量 |
| m 用于应用层 | d 用于中间件 | s 用于类名 |
| v 用于插件层 | q 用于脚本类 | o 回调函数 |
| | | p 用于POD结构体 |
| | | z 函数名 |
| | | u 共用体 |
| | | l 函数指针 |
| | | |
|
例如:
ass 用于类名
ast 用于变量类型
ase 用于枚举类型
asc 用于公开常量
axx 用于内部基类