目录
- //【重要】建议将public类型的接口写在前面,private类型的接口写在后面,类的成员变量尽量声明为private或protected属性。(1-16) [classInterface] [mVariableStatement]
示例:
class ClassA
{
public:
void Func1();
void Func2();
…
protected:
……
private:
int m_iX;
float m_fY;
…
}
示例:
void Test::Function()
{
// code block
}
void Test::Function2()
{
// code block
}
- //【优先】代码结构中,在逻辑上密切相关的语句之间不加空行;相对独立的程序块之间加空行进行分隔。(if、for、while、switch、do…while)(1-17) [blankLine]
示例:
void Function()
{
// code block
while (...)
{
...
}
// code block
}
示例:
int i_data = 0; // 定义时初始化。
int i_index = 0;
- ?【一般】同一逻辑的声明/定义块,建议尽量将初始化代码对齐,增加清晰度。(成员变量定义/声明,宏)TODO [left_Aligned]
示例:
DWORD dw_ret_val = 0;
DWORD* p_ret_val = NULL;
char* p_ret_val = NULL;
char c_ret_val = 0
- //【重要】If, for, while, do等语句独占一行,执行语句另起一行,即便执行语句只有一行也必须加分界符“{”和“}”。(1-4) (if、for、while、switch、do…while) (1-17)(增加对条件表达式占多行的判断) [delimiter] [executeStatement]
- //【重要】程序的分界符“{”和“}”应该独占一行且位于同一列,同时与引用他们的语句左对齐。(对齐TODO)(class、函数、if、for、while、switch、do…while、else)(1-17))[delimiter]
if示例:
if (condition)
{
if (bar)
{
Win();
}
else
{
Lose();
}
}
for示例:
for (int i = 0; i < count; i++)
{
...
continue;
}
while示例:
int i = 0;
while (i < count)
{
...
continue;
}
do…while示例:
do
{
...
break;
}while (i < count);
switch…case示例:
switch (index)
{
case value1:
{
...
break;
}
case value2:
{
i = value2;
break;
}
default:
{
i = 0;
break;
}
}
- //【重要】一行代码的最大长度限制建议设置为120,如果超过则需要进行分行。
- 【重要】长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首;拆分出的新行要进行适当的缩进,使排版整齐,语句可读。(当行宽度大于等于120,且操作符多于三个时,提示需要拆分且操作符位于行首)(1-17)[lineLength] [longExpression]
示例1:
if ((very_longer_variable1 >= very_longer_variable12)
&& (very_longer_variable3 <= very_longer_variable14)
&& (very_longer_variable5 <= very_longer_variable16))
{
dosomething();
}
示例:
namespace SpaceA
{
class ClassA
{
public:
void Func1();
……
private:
int m_iX;
};
}
- //【一般】代码缩进统一使用空格,一个Tab长度对应为4个空格。
- //【重要】关键字之后要留空格。(1-4) 强制为4个空格
像const、virtual、inline、case等关键字之后至少要留一个空格,否则无法辨析关键字;//像if、for、while 等关键字之后应留一个空格再跟左括号“(”,以突出关键字。(1-18)
示例:见第一章第3节条目(4)示例。
- //【重要】函数名之后不要留空格,紧跟左括号“(”。
示例:
void Function();
- //【一般】左括号“(”向后紧跟,右括号“)”、逗号“,”和分号“;”向前紧跟,紧跟处不留空格。
示例:
void Func1(int iX, int iY, int iZ);
- //【重要】逗号“,”之后要留空格。
示例:
Function(x, y, z);
- //【一般】如果分号“;”不作为一行的结束符号,其后要留空格。
示例:
for (initialization; condition; update);
- //【重要】赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符等二元操作符的前后应当加空格,如“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等。
示例:
if (currentCount > total);
x = a < b ? a : b;
- //【重要】一元操作符前后不加空格,如“!”、“~”、“++”、“--”、“&”(地址运算符)等。(1-4)
示例:
++a;
b--;
int* x = &y;
- //【重要】像“[]”、“.”、“->”这类操作符前后不加空格。(1-4)
示例:
i_array[5] = 0; // 不要写成 i_array [ 5 ] = 0;
object.Function(); // 不要写成 object . Function();
p_object->Function(); // 不要写成 p_object -> Function();
- 版本、版权声明;
- 函数接口声明;---------------TODO
- 重要的代码行或段落提示等。
- X【一般】尽量追求程序代码的逻辑清晰可读,以减少注释的辅助作用。
- X【一般】注释应该简单易于理解,遵循“KISS”(Keep It Simple and Stupid)原则。
- X【一般】尽量避免在注释中使用缩写,特别是不常用缩写。
- //【重要】注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。 (1-18)
示例:
// 一切正确,提交
if (...)
{
float f_balance; // Money in account
int i_min; // min number for …
int i_max; // max number for …
...
}
- //【重要】在文件的起始处建议写明文件注释,以简要说明文件/类的功能以及重要的大事件;对于一般的修改历史信息,建议使用版本控制工具记录。(1-4) introduction [introduction/copyright] [introduction/name] [introduction/description] [introduction/modifiedAndAuthor]
示例:
// ------------------------------------------------------------
// Copyright (c) 2015, 深圳开立科技有限公司。All rights reserved.
// 名 字: KUltraSoundImageSystem.h
// 描 述: 超声图像系统
// 修 改: 2015-06-18, zjm
// ------------------------------------------------------------
- ?【重要】公共接口必须提供注释,以方便用户的理解和使用。
示例:
// ------------------------------------------------------------
// 描述:获取血流体积的结果
// 参数:
// 输入:
// int x1: 第一点的x坐标相对于屏幕左上角的绝对坐标
// int y1: 第一点的y坐标相对于屏幕左上角的绝对坐标
// int x2: 第二点的x坐标相对于屏幕左上角的绝对坐标
// int y2: 第二点的y坐标相对于屏幕左上角的绝对坐标
// 输出:
// doubule* flowResult: flowResult[0]:线上的最大速度,
// flowResult[1]:线上的平均速度
// 返回值:bool, true--计算成功,false--计算失败
// 异常/错误:无
// ------------------------------------------------------------
- 【一般】对于类和接口的注释推荐使用doxygen软件标准格式,以方便生成软件文档。
- //【重要】全局变量、静态变量都要提供对应的注释。(1-29)
示例:
// 截断行变量。非0,截断行显示;0,连续行
int s_iTruncateLines;
- X【一般】尽量注释Why,而不仅仅是What。
示例:
// 根据需求文档XXXX条目的要求,对于缴税额大于1000万的,优惠5%
if (1000.00 <= fGrandTotal)
{
f_grand_total = fGrandTotal * 0.95;
}
- X【重要】修改代码的同时要修改相应的注释,以保证注释与代码的一致性。
- //【重要】不再有用的注释要及时删除。 (1-18) if 0
- X【一般】变量名用名词或者名词加形容词。
示例:
int i_old_value;
float f_page_time;
vector<AngleState> vct_angle_state;
map<string, int> map_calc_result;
- //【重要】变量前面加上类型的缩写。 (1-22)
标识符 | 说明 | 示例 |
p | pointer | Finger* p_rude; 某些情况下,p 和基本类型一起使 用只需要写明p即可。例如,int* p_counter |
str | string | string str_name; |
sz psz | ‘\0’结束的字符串 | char sz_name[10]; char* p_name; |
h | handle | HWND h_window; |
c | character (char) | char c_letter; |
uc | unsigned char | unsigned char uc_revdata[12] |
i | int | int i_counter; |
f | float | float f_salary; |
d | double | double d_commission; |
b | bool | bool b_isopen; |
u | unsigned | unsigned int ui_number; |
w | WORD | WORD w_value; |
l | long | long l_number; |
ll | long long | long long ll_num |
dw | DWORD or unsigned long integer | DWORD dw_longnum; |
e | enum | E_CALC_STAGE e_stage; |
- X【重要】用正确的反义词来描述具有相反意义或者互斥的变量。
示例:
int i_min_val;
int i_max_val;
int GetValue();
int SetValue(int iVal);
- X【一般】变量命名中使用特色命名或者缩写要有注释说明。
示例:
int i_mea_state; //测量状态
- 【重要】程序中不要出现仅依靠大小写区分的相似的标识符。
示例:
int i_param;//错误
int i_Param;//错误
- 【重要】程序中不要出现标识完全相同的局部变量和全局变量。
示例:
int g_iLastIndex;
int i_last_index;
- //【一般】变量中避免出现数字编号,除非逻辑上需要。(1-22)
示例:
int i_param1;//错误
int i_param2 //错误
- //【重要】变量的定义。 (1-23) [VariableName]
变量类型 | 定义描述 | 实例 |
全局变量 | 全局变量前面加上g_ | int g_iRecvData; |
静态变量 | 静态变量前面加上s_ | static int s_iRecvDate; |
成员变量 | 成员变量前面加上m_ | int m_iRecvData; |
局部变量 | 局部变量全部小写,单词之间用_分隔 | int i_last_index |
- //【重要】函数的参数首单词小写,其他单词首字母大写。 (1-22) [fn_parameter]
示例:
DrawText(const Point& point, int iOffset, const char* pRuleNo);
-
- 常量 [const]
- //【重要】常量全用大写字母并用下划线分割单词。(1-21)
示例:
const int MAX_DATA =1000;
- //【一般】尽量使用const替代#define定义常量。(1-21) [#define]
- //【重要】魔法数字用常量定义。 [magicNumber] (2-19)
示例:
const int MAX_PIXEL_COUNT =1000;
示例:
enum E_CALC_STAGE
{
E_CALC_STAGE_START =0,
E_CALC_STAGE_COLLECT_DATA,
E_CALC_STAGE_DOING,
E_CALC_STAGE_FINISHED,
E_CALC_STAGE_MAX,
}
- //【重要】函数每个单词的首字母大写其他小写。(1-18) 对函数名的判断,待检查TODO 3333 [fn_name]
示例:
void AddData(int iData);
- //【一般】函数最前面不要使用下划线。 (1-21)
- X【一般】全局函数的名字使用动词加名词。
示例:
void AddData(int iData);
- X【一般】成员函数使用动词即可。
示例:
data->Add();
- //【重要】类的名字由大写字母开头的单词组成。(1-21) [className]
示例:
Class KRecvData
{
……
}
- //【重要】类名最前面不加C,规定以“K”开头。(1-21) [className]
示例:
class K2DImgWin : public KImgWin
{
……
}
- //【一般】避免包含继承而来的基类名字。(1-21)[className]
示例:
错误示例
Class CreatureAnimal: public Creatrue
{
……
}
正确示例
Class KAinimal: public KCreatrue
{
……
}
- //【重要】文件名字由大写字母开头的单词组成。(1-15)只检查前两个字母 [filename]
示例:
KRecvData.h
KRecvData.cpp
- 【重要】使用宏定义表达式时,使用完备的括号。
示例:
#define RECTANGLE_AREA( a, b ) ((a) * (b))
- 【重要】将宏所定义的多条表达式放在大括号中。
示例:
#define INTI_RECT_VALUE( a, b ) \
{ \
a = 0; \
b = 0; \
}
- 【重要】函数必须声明返回值类型,如果没有返回值,则为void类型。
示例:
float GetValue();
void SetValue(int iWidth, int iHeight);
- 【一般】函数正常值用输出参数获得,而错误标志用返回值。
示例:
bool GetChar(char* pChar);
- 【重要】对于指针/引用方式传递的函数返回值,如果是只读用途,需要使用const修饰,以防止被意外修改。
示例:
const ClassA& Function();
示例:
void Function(int iVarOne, char cVarTwo);
- 【重要】只读用途的函数参数,需要使用const修饰,以防止被意外修改。
示例:
void Function(const ClassA& objectA);
- 【一般】避免将函数的参数作为工作变量,以防止内容被意外修改。
示例:
void sum_data(int* pSum, unsigned int uiNum, int* pData)
{
//创建局部变量作为工作变量,以防止sum被意外修改
int i_sum_temp = 0;
for (unsigned int i = 0; i < uiNum; i++)
{
i_sum_temp += pData[i];
}
//局部变量完成工作后,再赋值给sum
*pSum = i_sum_temp;
}
- 【一般】避免使用类型和数目不确定的参数。
示例:
//避免设计类似下面的函数参数
int printf(const chat* pFormat[, argument]…);
示例:
class Stack
{
public:
void Push(int iElem);
int Pop();
int GetCount() const; // const 成员函数
private:
int m_iNum;
int m_iData[100];
};
int Stack::GetCount() const
{
++ m_iNum; // 编译错误,企图修改数据成员m_num
Pop(); // 编译错误,企图调用非const 函数
return m_iNum;
}
- 【优先】在函数体的“入口处”,对函数输入数据的有效性进行检查,包括参数数据和非参数数据(函数体内使用的全局变量、数据文件等)。
示例:
bool ProcessFunc(const unsigned char* pParamIn, unsigned char* pParamOut)
{
// 入口处使用断言判定参数的有效性,且最好每次只判断一个条件
KASSERT(NULL != pParamIn);
KASSERT(NULL != pParamOut);
//使用断言判定全局变量的合法性;
KASSERT(0 < g_iNumVar);
bool b_ret = true;
……
return b_ret;
}
- 【一般】在函数体的“出口处”,对返回值的有效性进行检查。
示例:
int GetCurrentNum(int iBegin, int iEnd, int iOffset)
{
int i_cur_num = 0;
i_cur_num = iBegin + iOffset;
//使用断言判定返回值的有效性;
KASSERT(i_cur_num <= iEnd);
return i_cur_num;
}
- 【重要】函数完成的功能要单一,符合“单一职责”原则。
示例:
更改密码的处理流程,分成两个功能单一的函数,而不是写在同一个函数中。
bool ValidateOldPassword(const String strOldPwd)
{
// 验证用户输入的旧密码的合法性
……
}
bool SetNewPassword(const String strNewPwd)
{
// 设置用户输入的新密码
……
}
- 【一般】尽量不要编写依赖于其他函数内部实现的函数,减少函数之间的耦合。
示例:
如下是在DOS下TASM的汇编程序例子。过程Print_Msg的实现依赖于Input_Msg的具体实现,这种程序是非结构化的,难以维护、修改。
... // 程序代码
proc Print_Msg // 过程(函数)Print_Msg
... // 程序代码
jmp LABEL
... // 程序代码
endp
proc Input_Msg // 过程(函数)Input_Msg
... // 程序代码
LABEL:
... // 程序代码
endp
- 【一般】尽量避免函数的“记忆”功能,相同的输入产生相同的输出。
示例:
unsigned int IntegerSum(unsigned int uiBase)
{
// 注意,使用static类型局部变量使函数返回值不可预测
static unsigned int s_ui_sum = 0;
for (unsigned int i = 1; i <= uiBase; i++)
{
s_ui_sum += uiIndex;
}
return s_ui_sum;
}
- 【优先】编写可重入函数时,若使用全局变量或静态变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。
示例:
unsigned int Example(int iParam)
{
unsigned int ui_temp;
// 若申请不到“信号量”,说明另外的进程正处于给Exam赋值并计算其平方过程中(即正在使用此信号),本进程须等待其释放信号后,才可继续执行。若申请的到信号,则可继续执行,但其它进程必须等待本进程释放信号后,才能再使用本信号
[申请信号量操作]
Exam = iParam;
ui_temp = SquareExam();
[释放信号量操作]
return ui_temp;
}
- //【重要】尽量减小函数体的体量,以提高代码的可读性,建议单个函数体不超过80行。
- 【优先】禁止使用return 语句返回指向“栈内存”的指针或引用。
示例:
char* GetString()
{
char c_arrary[] = "hello world";
// 错误,函数运行结束时p数组将被销毁
// 编译器将提出警告
return c_arrary;
}
- 基本语句
- if语句
- 【重要】不可将布尔型变量直接与TRUE和FALSE或者0、1比较。
- 【重要】将整形变量用“==”或“!=”直接与0比较。
示例:
int i_value = 121;
不可模仿布尔变量的风格
if(!i_value)
if(i_value)
- 【重要】不可将浮点变量用==或!=与任何数字比较。
- 【一般】浮点变量的精度,不能一概而论,根据具体业务来定义。
- 【重要】应当将指针变量用“==”或者“!=”与NULL比较而不是与0比较。
- 【一般】if语句“==”比较时,常量写在左边,变量写在右边。
示例:
if(0 == i_value)
- 【一般】if语句中不要直接用函数与变量比较,应该将函数返回值与变量比较。
示例:
int i_index = GetIndex();
if(0 == i_index)
{
……
}
- 【一般】多个if语句判断加括号区分。
示例:
const int MAX_DATA = 100;
const int MIN_DATA = 1;
if((i_data > MIN_DATA) && (i_data < MAX_DATA))
{
……
}
- 【优先】每一个case语句结尾不要忘记加break。
- 【优先】即使程序不需要default处理,也要保留default:break,每一个case都加上{}。
示例:
switch(e_value)
{
case E_VALUE_ONE:
{
……
break;
}
case E_VALUE_TWO:
{
……
break;
}
case E_VALUE_THREE:
{
……
break;
}
default :
{
……
break;
}
}
- 【一般】switch语句的case不能大于10个,否则使用表驱动。
示例:
// 根据关键字,执行处理函数
bool TableDrive::HandleKeyword(int iKeyWord)
{
typedef map<int, PHandle>::const_iterator CI;
CI iter = m_KeyToHandle.find(iKeyWord);
// 没有搜索到关键字
if (m_KeyToHandle.end() == iter)
{
printf("\n @@ search Keyword %d fail!\n", iKeyWord);
return false;
}
// :TRICKY: 注意成员函数指针的引用格式
PHandle p_function = iter->second;
return (this->* p_function)();
}
bool TableDrive::MapKeyToHandle()
{
m_KeyToHandle[KEYWORD_A] = &TableDrive::HandleKeyA;
m_KeyToHandle[KEYWORD_B] = &TableDrive::HandleKeyB;
m_KeyToHandle[KEYWORD_C] = &TableDrive::HandleKeyC;
return true;
}
- 【一般】不可在循环体内修改循环变量。
- 【一般】for语句的循环控制变量的取值采用“半开半闭区间”写法。
示例:
const int MAX_DATA = 5;
for(int i = 0; i < MAX_DATA; i++)
{
……
}
- 【重要】原则上内存是一项很宝贵的资源,应该尽量节约使用,或重复利用。
- 【重要】一般而言,对于占用空间较小、使用频繁且生命周期固定的变量,建议使用栈内存;而对于占用空间太大或者生命周期比较复杂的变量,需要使用堆内存。
示例1:
//栈上分配内存
char c_id_array[3];
c_id_array [0] = ssid[i+2];
c_id_array [1] = ssid[i+3];
c_id_array [2] = '\0';
示例2:
//堆上分配内存
//占用空间大的内存分配
char* p_mark_string = new char[MAX_MARK_LEN];
memset(p_mark_string, 0, MAX_MARK_LEN);
//生命周期复杂的对象new在堆上
KCinePlaybackState* p_state = new KCinePlaybackStateManual(
m_pCinePlaybackController, dispModeContext->GetCinePlayer());
KASSERT(NULL != p_state);
m_pCinePlaybackController->SetCurState(p_state);
- 【重要】如果一个指针/引用在多个类中使用,需要统一维护该内存的生命周期。
示例:
KBackEndSubSystem::KBackEndSubSystem()
{
//显示模式上下文指针,初始化
m_pDispModeContext = new KDispModeContext(this, m_Layout);
……
}
KBackEndSubSystem::~KBackEndSubSystem()
{
//该指针在后端图像系统的很多类中使用,但是统一由所属的后端子系统维护和管理内存;
RELEASE_POINTER(m_pDispModeContext);
……
}
- 【优先】所有变量使用前都要进行初始化。
示例1:
ClassA* p_obj_a = new ClassA();
示例2:
char* p_buf = new char[MAX_LENGTH];
memset(p_buf, 0, MAX_LENGTH);
- 【优先】类的构造函数要对所有数据成员初始化,且建议使用初始化表。
示例:
class F
{
public:
F(int x, int y); // 构造函数
private:
int m_x;
int m_y;
}
F::F(int x, int y)
: m_x(x), m_y(y) //初始化表
{
……
}
- 【优先】类的初始化时,如果存在继承关系,派生类必须在其初始化表里调用基类的构造函数。
示例:
class A
{…
A(int x); // A 的构造函数
};
class B : public A
{…
B(int x, int y);// B 的构造函数
};
B::B(int x, int y)
: A(x) // 在初始化表里调用A 的构造函数
{
…
}
- 【重要】对于含有指针成员的类,要注意确认是否需要重写类的构造函数、拷贝构造函数和拷贝赋值函数等。
- 【优先】指针类型的变量在使用前一定记得先判断是否为NULL,避免使用空指针。
示例:
KASSERT(m_pContext != NULL);
KASSERT(m_pContext->Get2DImg() != NULL);
m_pContext->Get2DImg()->Show();
- 【优先】防止内存操作越界。
示例1:
void RecordValue(const int iIndex, const unsigned long ulValue)
{
const MAX_ARRAY_NUM = 100;
static unsigned long s_ul_array[MAX_ARRAY_NUM];
……
//使用断言防卫数组越界
KASSERT(0 <= iIndex)
KASSERT(MAX_ARRAY_NUM > iIndex);
s_ul_array [iIndex] = ulValue;
……
}
示例2:
void ProcessFunction(unsigned char* pSrc)
{
char c_buffer[MAX_LENGTH] = {0};
//内存数据的拷贝,注意不要越界
memcpy(c_buffer, pSrc, sizeof(c_buffer));
……
}
- 【优先】多线程对内存数据进行并发访问前,记得要加锁保护。
示例:
void KThread::AddThreadToMap(unsigned int uiTid)
{
// 加入Map
KThreadInfo info = {this, 0};
g_threadMapMutex.Lock(); // 加锁
g_threadMap.insert(std::pair<unsigned int, KThreadInfo>( uiTid, info));
g_threadMapMutex.Unlock(); // 解锁
}
示例:
ClassA *p_obj_a = new ClassA;
……
delete p_obj_a;
p_obj_a = NULL;
- 【优先】对于对象数组,需要在new/delete 中配对使用”[]”。
示例:
char* p_mark_str = new char[MAX_MARK_LEN];
……
delete []p_mark_str;
p_mark_str = NULL;
- 【优先】释放内存后要立即将指针置为NULL,防止产生野指针。
示例:
#define RELEASE_POINTER(p) \
if(p) \
{ \
delete p; \
p = NULL; \
}
- 【重要】对于频繁使用的堆内存,尽量避免重复new和delete,以避免内存碎片且低效率。
- 【优先】编码过程中要求使用防卫式编程方法。
- 子程序应该不因传入数据的错误而受到破坏,例如,使用空指针、数组越界,导致程序发生段错误;
- 对于编写函数所基于的任何假定,都应该使用断言等手段进行防卫,保证程序的安全;
- 防止垃圾数据进入,同时保证不产生垃圾数据。
- 【优先】断言使用公司自定义的KASSERT。
- 【优先】断言仅是对程序安全性的检查和保证,禁止在其中执行必要的业务逻辑。
示例:
bool KBackEndSystem::SaveCineFile()
{
DataTree tree("BackEndSystem");
int i_probe_id = KProbeManager::GetInstance()->GetCurProbeId();
//错误!!!把必要执行的业务流程放在了断言中,如果release版本中关闭了断言的话,
//就会导致业务流程的缺失,造成严重的错误!
KASSERT(tree.AddElement("ProbeId", i_probe_id));
KASSERT(tree.AddElement("Row", m_pWindowSwitchContrl->RowNum()));
KASSERT(tree.AddElement("Column", m_pWindowSwitchContrl->ColNum()));
KASSERT(tree.AddElement("TGCStatus", m_bTGCShowStatus));
……
}
- 【优先】使用指针时,要用断言进行判空处理。
示例:
KASSERT(NULL != p_value);
- 【优先】多个参数合法性的判断,要分开使用断言。
示例:
KASSERT(b < a);
KASSERT(b > c);
- 【优先】函数的入参用断言进行合法性判断。
示例:
const PdbFracFilterPara& GetFracFilterPara(int iLevel=0) const
{
KASSERT(iLevel < m_nNumOfFracFilterPara);
KASSERT(iLevel >= 0);
……
return m_pdbSysCommon.fracfilterpara(iLevel);
}
- 【重要】函数返回值,如果非预期, 则直接用断言对此返回值进行合法判断。
示例:
const int MAX_DATA = 100;
int i_data = GetData();
KASSERT (i_data < MAX_DATA);
- 【重要】函数的出参,如果非预期, 则直接用断言对此返回值进行合法判断。
示例:
const int MAX_DISTANCE = 15;
int i_distance;
CalcDistance(&i_distance);
KASSERT(i_distance < MAX_DISTANCE);
- 【重要】参数除法计算,断言校验除0等异常情况。
示例:
float f_x;
float f_y;
KASSERT(f_x <= EPSINON);
KASSERT(f_x >= -EPSINON);
float f_ratio = f_y / f_x;
示例:与外部系统进行dicom通信错误给出提示
void DcmCmdSender::Run()
{
…
if (DCM_CMD_TYPE_MPPS == str_cmd_type)
{
i_ret = SendMppsCommand(strCmdUID, strErrMsg);
}
//组合命令执行结果
mapDcmCmdState[DCM_FIELD_QUEUE_STATUS] = QUEUE_STATUS_FINISH;
//队列状态:已结束
if (ERR_OK == i_ret)
{
//命令状态:成功,结果:成功
……
}
else
{
//命令状态:失败,结果:错误信息
LOGW(“send mpps error,%s”, strErrMsg);
}
}
- 【优先】设备状态出错,给出异常提示。
示例:超声设备运行过程中,断网给出异常提示
NET_STATE GetNetWorkStatus()
{
E_WIFI_STATE e_wifi_state;
…
// whether wifi is plug in and enabled
if((e_wifi_state == E_WIFI_STATE_NOT_EXIST)
|| (e_wifi_state == E_WIFI_STATE_NOT_UP))
{
LOGW("network is down");
return NET_STATE_ETH_UNCONNECT;
}
return NET_STATE_WIFI_AVAILABLE;
}
- 【优先】读取文件异常,给出异常提示。
示例:加载回放电影异常,给出错误提示
bool KBackEndSystem::LoadCineFile(KCineFileType::Type eCinetype,
const char* pFilePath,
KCineFileLoadResult::Result& eErrCode)
{
……
DataTree tree("BackEndSystem");
string str_path = string(pFilePath) + "_backendsystem.idx";
b_status &= tree.LoadDataFromFile(str_path.c_str());
if(!b_status)
{
LOGW("Load Cine File err,errcode:%d", b_status);
}
……
return b_status;
}
- 【优先】图像数据上传解析异常,给出提示。
示例:PW模式下,解析数据出错,给出错误提示
bool KPWImgPacketParse::ParseImgData(unsigned char* pData)
{
……
KPWFrameData* p_frame_data = mImgFramBuf.GetBuffer(1);
if (NULL == p_frame_data)
{
LOGW("KImgPacketParse: PW parse and preprocess lost one frame");
return false;
}
……
return true;
}
- 【优先】所有工程,打印统一接口,模块调试完成,关闭打印日志。
- 【优先】一般的调试信息,不要打印日志,以免程序影响效率。
- 【优先】对于频繁调用接口,接口里面不要加打印日志。
- 【重要】日志要分级,一般情况下,日志中只记录警告信息和错误信息。
示例:
//执行sql语句
int DbSqlite::Exec(const char* pSql)
{
……
i_Ret = sqlite3_exec(m_pDatabase, str_Sql.c_str(), NULL, NULL, p_err_msg);
………
if (NULL != p_err_msg)
{
……
sqlite3_free(p_err_msg);
LOGW("Failed to exec sql: %s ", str_Sql.c_str());
}
……
}
- 【重要】程序运行的关键参数需要记录在日志中,便于后续协助定位程序异常。
示例:
void KeyboardBrightAutoSense::Init()
{
……
i_base_bright = KAdjustAudioBright::ReadDefaultBrightFromEEPROM();
LOGI("i_base_bright = %d", i_base_bright);
}
示例:
DICOM传输过程中的错误码定义
#define PATIENT_ERR_CODE(code) (-((MODULE_PATIENT << 8) | (code)))
//不支持该操作
const int ERR_NOT_SUPPORT = PATIENT_ERR_CODE(0x01);
//无效的UID
const int ERR_INVALID_UID = PATIENT_ERR_CODE(0x02);
//操作被用户取消
const int ERR_USER_CANCEL_OPERATION = PATIENT_ERR_CODE(0x03);
……
//构造数据集失败
const int ERR_BUILD_DATASET_FAIL = PATIENT_ERR_CODE(0xB5);
//填充数据集失败
const int ERR_FILL_DATASET_FAIL = PATIENT_ERR_CODE(0xB6);
//加载DICOM文件失败
const int ERR_LOAD_DICOM_FILE_FAIL = PATIENT_ERR_CODE(0xB7);
//加载DICOM图像失败
const int ERR_LOAD_DICOM_IMAGE_FAIL = PATIENT_ERR_CODE(0xB8);
//Dicom文件不存在
const int ERR_DICOM_FILE_NOT_EXIST = PATIENT_ERR_CODE(0xB9);
int DcmImageExtractor::SaveAsCine(const std::string& strFilePath,
const std::string& strCineFormat,
const bool& bIsCancel)
{
if (!m_bFileLoaded)
{
LOGE("dcm file load failed!");
return ERR_LOAD_DICOM_FILE_FAIL;
}
……
}
- 【优先】对于非预期的错误,并在日志文件中记录,程序退出。
示例:
const int MAX_DISTANCE = 28;
int i_data = GetCurDistance();
if(i_data > MAX_DISTANCE)
{
LOGE(“cur distance is out of range”)
return;
}
- 【重要】在满足正确性、可靠性、健壮性和可读性等质量因素的前提下,提高程序效率。
- 【重要】提高程序的全局效率为主,局部效率为辅,不能为了提高局部效率而降低程序的全局效率。
- 【一般】先确定程序的“效率瓶颈”,再着手进行效率优化。
- 【重要】除非能够确定某处代码是程序的“效率瓶颈”,否则不能为了追求效率的优化而影响代码的可读性。
- 【一般】先优化数据结构和算法,再优化执行代码。
- 【重要】尽量充分利用硬件资源,一般使用硬件可以完成的工作,尽量使用硬件而不是软件。
- 【重要】多线程使用时,尽量减小占用的CPU资源;对于空闲的线程,优先选用通知激活的方式,避免选用线程空转;对于只使用一次的线程,尽量复用现有线程。
- 【一般】通常情况下需要对内存进行对齐,以提高访问效率;如果对空间效率要求比较高,可以不进行对齐。
示例:
#pragma pack(4) //指定4字节对齐
- 【重要】尽量减小变量的作用域,在真正使用该变量时才进行声明和初始化。
示例:
// 不要在程序起始处或者变量使用之前声明和初始化
// int i_index = 0;
// bool b_cond = true;
…… // do something
// 在真正开始使用的地方创建
bool b_cond = true;
for(int i_index = 0; i_index < MAX && b_cond; i_index++)
{
……
}
- 【重要】函数的返回值和参数,如果能够使用“引用传递”的方式,尽量避免使用“值传递”。
示例:
String& String::operate=(const String& strOther);
注意:该条目不适用于基本数据类型、STL迭代器和函数对象;
- 【一般】尽量少做转型动作。
示例:
对于继承于Window的SpecialWindow:
typedef std::vector<std::tr1::shared_ptr<Window>> VPW;
VPW vct_win_ptrs;
……
for (VPW::iterator iter = vct_win_ptrs.begin();
iter != vct_win_ptrs.end(); ++iter)
{
if(SpecialWindow* p_sw = dynamic_cast<SpecialWindow*>(iter->get()))
{
p_sw->blink();
{
}
直接使用派生类的指针会更好一些:
typedef std::vector<std::tr1::shared_ptr< SpecialWindow>> VPSW;
VPSW vct_win_ptrs;
……
for (VSPW::iterator iter = vct_win_ptrs.begin();
iter != vct_win_ptrs.end(); ++iter)
{
(*iter)->blink();
}
- 【重要】尽量减少循环嵌套层次,谨慎使用超过两层的循环嵌套。
- 【一般】在多层循环中,尽量将最忙的循环放在最内层,以减少CPU跨切循环层的次数。
示例:
const int ROW_NUM = 100;
const int COL_NUM = 5;
for (int i = 0; i < ROW_NUM; i++)
{
for (int j = 0; j < COL_NUM; j++)
{
i_sum += getAddValue(i, j);
}
}
可以改为如下方式,以提高效率:
for (int j = 0; j < COL_NUM; j++)
{
for (int i = 0; i < ROW_NUM; i++)
{
i_sum += getAddValue(i, j);
}
}
- 【重要】合理安排循环和判断语句的层次结构,尽可能将判断语句放在循环体之外。
示例:
for (int i = 0; i < MAX_RECT_NUMBER; i++)
{
if (eDataType == RECT_AREA)
{
i_area_sum += i_rect_area[i];
}
else
{
i_rect_length_sum += rect[i].m_iLength;
i_rect_width_sum += rect[i].m_iWidth;
}
}
因为判断语句与循环变量无关,故可以进行如下改进,以减少判断次数:
if (eDataType == RECT_AREA)
{
for (int i = 0; i < MAX_RECT_NUMBER; i++)
{
i_area_sum += i_rect_area[i];
}
}
else
{
for (int i = 0; i < MAX_RECT_NUMBER; i++)
{
i_rect_length_sum += rect[i].m_iLength;
i_rect_width_sum += rect[i].m_iWidth;
}
}
- 【重要】循环体内工作量最小化。
示例:
for (int i = 0; i < MAX_ADD_NUMBER; i++)
{
i_sum += i;
//以下语句放到循环体外更高效
//i_back_sum = i_sum;
}
i_back_sum = i_sum;
- 【重要】对于循环体内重复进行的运算,考虑是否可以提取到循环体外完成。
示例:
for(int i = 0; i < GetMaxCount(iArray); i++)
{
……
}
可以把循环次数的计算提取到循环体外:
int i_cnt = GetMaxCount(iArray);
for (int i = 0; i < i_cnt; i++)
{
……
}
- 【一般】通过对系统数据结构的划分与组织的改进,以及对程序算法的优化来提高空间效率。
示例:
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef struct StudentScoreStru
{
BYTE m_cName[8];
BYTE m_cAge;
BYTE m_cSex;
BYTE m_cClass;
BYTE m_cSubject;
float m_fScore;
} StudentScore;
因为每位学生都有多科学习成绩,故如上结构将占用较大空间;应如下改进(分为两个结构),总的存贮空间将变小,操作也变得更方便:
typedef struct StudentStru
{
BYTE m_cName[8];
BYTE m_cAge;
BYTE m_cSex;
BYTE m_cClass;
} Student;
typedef struct StudentScoreStu
{
WORD m_usStudentIndex;
BYTE m_cSubject;
float m_fScore;
} StudentScore;
- 【重要】头文件中尽量不引用其他头文件,以降低文件间的编译依存关系。
示例:
//#include “KPWImgWin”
//#include “IFePWParamSet”
//不使用头文件的引用,改为对应类的前置声明即可
class KPWImgWin;
class IFePWParamSet;
class IPWImgWinFacade : public IImgWinFacade
{
public:
……
private:
const KBeDParamPack* m_pBeParam;
const IFePWParamSet* m_pFeParam;
};
- 【优先】头文件中#include其他头文件时,要使用预编译语句#pragma once进行限定,以提高编译效率。
示例:
#pragma once
#include “KObject.h”
- 【优先】使用#include包含头文件时,不要包含文件的路径,以避免对目录结构的依赖。
示例:
#include “./include/HeaderFile.h”
直接使用文件名即可
#include “HeaderFile.h”
- 【重要】头文件中尽量避免对using namespace的使用,以减少命名空间的扩散。
示例:
using namespace std::tr1;
shared_ptr<Investment> m_pInvestment;
改为使用完整命名
std::tr1::shared_ptr<Investment> m_pInvestment;
- 【重要】尽量减小文件内容的长度,建议单个文件以不超过1200行代码为宜,配置文件超过1200行需要特殊说明。
- 【一般】避免编写技巧性很高的代码,以提高代码的可读性。
示例:
*i_stat_poi++ +=1;
改写成
*i_stat_poi +=1;
i_stat_poi++;
- 【重要】当心变量发生上溢或下溢。
示例:
char c_letter = 127;
c_letter++; //上溢
char c_letter = -128;
c_letter--; 下溢