代码原则三

【规则6-1】一条语句只完成一个功能。
说明:复杂的语句阅读起来,难于理解,并容易隐含错误。变量定义时,一行只定义一个变量。
正例:
aiAge[i] = i;
i++;
反例:
aiAge[i] = i++; // 一条语句实现了多个功能
正例:
int iHelp;
int iBase;


int iResult;
iHelp = iBase;
iResult = iHelp + GetValue(&iBase);
反例:
int iBase , iResult; // 一行定义多个变量
iResult = iBase + GetValue(&iBase); // 一条语句实现了多个功能
【规则6-2】在表达式中使用括号,使表达式的运算顺序更清晰。
说明:由于将运算符的优先级与结合律熟记是比较困难的,为了防止产生歧义并提高可读性,即使不加括号时运算顺序不会改变,也应当用括号确定表达式的操作顺序。
正例:
if (((iYear % 4 == 0) && (iYear % 100 != 0)) || (iYear % 400 == 0))
反例:
if (iYear % 4 == 0 && iYear % 100 != 0 || iYear % 400 == 0)
【规则6-3】避免表达式中的附加功能,不要编写太复杂的复合表达式。
说明:带附加功能的表达式难于阅读和维护,它们常常导致错误。一个好的编译器下面两种情况产生代码的效果是一样的,不论它是一个大的表达式或是多个连续执行的简单的表达式。
正例 :
aiVar[1] = aiVar[2] + aiVar[3];
aiVar[4]++;
iResult = aiVar[1] + aiVar[4];
aiVar[3]++;
反例:
iResult = (aiVar[1] = aiVar[2] + aiVar[3]++) + ++aiVar[4] ;
【规则6-4】不可将布尔变量直接与TRUE、FALSE或者1、0进行比较。
正例:
设bFlag 是布尔类型的变量
if (bFlag) // 表示flag为真
if (!bFlag) // 表示flag为假


设bFlag 是布尔类型的变量
if (bFlag == TRUE)
if (bFlag == 1)
if (bFlag == FALSE)
if (bFlag == 0)
【规则6-5】在条件判断语句中,当整型变量与0 比较时,不可模仿布尔变量的风格。
说明: 应当将整型变量用“==”或“!=”直接与0比较。
正例:
if (iValue == 0)
if (iValue != 0)
反例:
if (iValue) // 会让人误解 iValue是布尔变量
if (!iValue)
【规则6-6】不可将浮点变量用“==”或“!=”与任何数字比较。
说明:无论是float还是double类型的变量,都有精度限制。所以一定要避免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。
正例:
if ((fResult >= -EPSINON) && (fResult <= EPSINON))
反例:
if (fResult == 0.0) // 隐含错误的比较
其中EPSINON是允许的误差(即精度)。
【规则6-7】应当将指针变量用“==”或“!=”与NULL比较。
说明:指针变量的零值是“空”(记为NULL)。尽管NULL的值与0相同,但是两者意义不同。
正例:
if (pHead == NULL) // pHead与NULL显式比较,强调pHead是指针变量
if (pHead != NULL)
反例:
if (pHead == 0) // 容易让人误解pHead是整型变量
if (pHead != 0)
或者
if (pHead) // 容易让人误解pHead是布尔变量
if (!pHead)


【规则6-8】 禁止将逻辑表达式与TRUE 比较,而应该与FALSE比较或直接使用它们不进行比较。
说明:逻辑表达式的结果是布尔类型,根据布尔类型的语义,零值为“假”(记为FALSE),任何非零值都是“真”(记为TRUE)。TRUE的值究竟是什幺并没有统一的标准。例如Visual C++ 将TRUE定义为1,而Visual Basic则将TRUE定义为-1。
通常情况下,一个逻辑表达式不需要与TRUE 或FALSE 进行比较,因为逻辑表达式的值就是TRUE 或FALSE。
反例:
BOOLEAN bFound = FALSE;
if (!bFound == TRUE) // 比较的结果是 FALSE ,因为 !0不等于 1
...
【规则6-9】 在switch语句中,每一个case分支必须使用break结尾,最后一个分支必须是default分支。
说明:避免漏掉break语句造成程序错误。同时保持程序简洁。
正例:
switch (iMessage)
{
case SPAN_ON:
{
[处理语句]
break;
}
case SPAN_OFF:
{
[处理语句]
break;
}
default:
{
[处理语句]
}
}
【规则6-10】 不可在for 循环体内修改循环变量,防止for 循环失去控制。


〖建议6-1〗 循环嵌套次数不大于3次。
说明:保持程序简洁。
〖建议6-2〗 do while语句和while语句仅使用一个条件。
说明:保持程序简洁。如果需要判断的条件较多,建议用临时布尔变量先计算是否满足条件。
正例:
BOOLEAN bCondition;
do
{
……..
bCondition = ((tAp[iPortNo].bStateAcpActivity != PASSIVE)
|| (tAp[iPortNo].bStateLacpActivity != PASSIVE))
&& (abLacpEnabled[iPortNo])
&& (abPortEenabled[iPortNo])
} while (bCondition);
〖建议6-3〗当switch语句的分支比较多时,采用数据驱动方式。
说明:当switch 语句中case 语句比较多时,会降低效率和程序的结构清晰性。
正例:
extern void TurnState(void);
extern void SendMessage (void);
…….
void (*StateChange[20])() = {TurnState, SendMessage, NULL, TurnState… };

if (StateChange[iState])
{
(*StateChange[iState])();
}
〖建议6-4〗 在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少CPU跨切循环层的次数。
正例:
for (iCol = 0; iCol < 5; iCol++)
{
for (iRow = 0; iRow < 100; iRow++)
{


iSum = iSum + aiDate[iRow][iCol];
}
}
反例:
for (iRow = 0; iRow < 100; iRow++)
{
for (iCol = 0; iCol < 5; iCol++)
{
iSum = iSum + aiDate[iRow][iCol];
}
}
〖建议6-5〗 如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体的外面。
说明:下面两个示例中,反例的程序比正例多执行了num-1次逻辑判断。并且由于前者总要进行逻辑判断,打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。如果num非常大,最好采用示例(2)的写法,可以提高效率。如果N非常小,两者效率差别并不明显,采用示例(1)的写法比较好,因为程序更加简洁。
const int NUM = 100000;
正例:
if (bCondition)
{
for (i = 0; i < NUM; i++)
{
DoSomething();
}
}
else
{
for (i = 0; i < NUM; i++)
{
DoOtherthing();
}
}
反例:
for (i = 0; i < NUM; i++)
{
if (bCondition)
{
DoSomething();
}
else
{


DoOtherthing();
}
}
〖建议6-6〗 for语句的循环控制变量的取值采用“半开半闭区间”写法。
说明:这样做更能适应c语言数组的特点,c语言的下标属于一个“半开半闭区间”。
正例:
int aiScore[NUM];

for (i = 0; i < NUM; i++)
{
printf(“%d\n”,aiScore[i])
}
反例:
int aiScore[NUM];

for (i = 0; i <= NUM-1; i++)
{
printf(“%d\n”,aiScore[i]);
}
相比之下,正例的写法更加直观,尽管两者的功能是相同的。
〖建议6-7〗 在进行”==”比较时,不要写成”=”。
说明:可以采用一些技巧,让编译器去发现错误。
正例:
if (NULL == pTail)
if (0 == iSum)
示例中有意把p和NULL颠倒。编译器认为 if (pTail = NULL) 是合法的,但是会指出 if (NULL = pTail)是错误的,因为NULL不能被赋值。


7. 函数与过程
函数是C/C++程序的基本功能单元,是构建大厦的基石。如何编写出正确、高效、易维护的函数是软件编码质量控制的关键。一个函数包括函数头,函数名,函数体,参数,返回值。其中函数头的编写参见第三章注释,函数名参见第四章命名规则,本章着重描述作为接口要素的参数和返回值,函数体的实现以及函数相互之间的调用关系。
7.1 参数
说明:函数在说明的时候,可以省略参数名。但是为了提高代码的可读性,要求不能省略。
正例:
void SetValue(int iWidth, int iHeight);
float GetValue(void);
反例:
void SetValue(int, int);
float GetValue();
说明:防止该指针在函数体内被意外修改。
正例:
int GetStrLen(const char *pcString);
说明:一个函数被调用的时候,形参会被一个个压入被调函数的堆栈中,在函数调用结束以后再弹出。一个结构所包含的变量往往比较多,直接以一个结构为参数,压栈出栈的内容就会太多,不但占用堆栈空间,而且影响代码执行效率,如果使用不当还可能导致堆栈的溢出。如果使用结构的指针作为参数,因为指针的长度是固定不变的,结构的大小就不会影响代码执行的效率,也不会过多地占用堆栈空间。
【规则7-1-1】不能省略参数名,如果函数没有参数,则用void填充。
【规则7-1-2】如果参数是指针,且仅作输入用,则应在类型前加const。
【规则7-1-3】当结构变量作为参数时,应传送结构的指针而不传送整个结构体。
〖建议7-1-1〗避免函数有太多的参数,参数个数尽量控制在5个以内。
说明:如果参数太多,在使用时容易将参数类型或顺序搞错,而且调用的时候也不方便。如果参数的确比较多,而且输入的参数相互之间的关系比较紧密,不妨把这些参数定义成一个结构,然后把结构的指针当成参数输入。


说明:参数的顺序要遵循程序员的习惯。如输入参数放在前面,输出参数放在后面等。
正例:
int RelRadioChan(const T_RelRadioChanReq *ptReq, T_RelRadioChanAck *ptAck);
说明:编译器不对可变参数个数的函数调用作类型检查和参数检查。这种风格的函数在编译时丧失了严格的类型安全检查。
说明:一方面因为BOOLEAN参数值无意义,TRUE/FALSE的含义是非常模糊的,在调用时很难知道该参数到底传达的是什么意思;其次BOOLEAN参数值不利于扩充。
7.2 返回值
说明:C语言中,凡不加类型说明的函数,一律自动按整型处理。如果不注明类型,容易被误解为void类型,产生不必要的麻烦。
C++语言有很严格的类型安全检查,不允许上述情况发生。由于C++程序可以调用C函数,为了避免混乱,规定任何C/ C++函数都必须有类型。如果函数没有返回值,那么应声明为void类型。
说明:为了保证对被调用函数返回值的判断,有返回值的函数中的每一个退出点都需要有返回值。
〖建议7-1-2〗参数命名顺序要合理。
〖建议7-1-3〗尽量不要使用类型和数目不确定的参数。
〖建议7-1-4〗避免使用BOOLEAN参数。
【规则7-2-1】不要省略返回值的类型,如果函数没有返回值,那么应声明为void类型。
【规则7-2-2】对于有返回值的函数,每一个分支都必须有返回值。
〖建议7-2-1〗如果返回值表示函数运行是否正常,规定0为正常退出,非0为异常退出。不要使用TRUE或FALSE作为返回值。
说明:在需要判断函数是否正常运行的情况下,就需要分析函数的返回值。在C语言中,根据布尔类型的语义,零值为“假”(记为FALSE),任何非零值都是“真”(记为TRUE)。TRUE的值究竟是什么并没有统一的标准。例如Visual C++将TRUE定义为1,而Visual Basic则将TRUE定义为-1。


int SubFunction(void);
反例:
BOOLEAN SubFunction(void);
7.3内部实现
函数体的实现并不是随心所欲,而是有一定的规矩可循。不但要仔细检查入口参数的有效性和精心设计返回值,还要保证函数的功能单一,具有很高的功能内聚性,尽量减少函数之间的耦合,方便调试和维护。
说明:很多程序错误是由非法参数引起的,我们应该充分理解并正确处理来防止此类错误。
【规则7-3-1】对输入参数的正确性和有效性进行检查。
【规则7-3-2】防止将函数的参数作为工作变量。
说明:将函数的参数作为工作变量,有可能错误地改变参数内容,所以很危险。对必须改变的参数,最好先用局部变量代之,最后再将该局部变量的内容赋给该参数。
正例:
void SumData(int iNum, int *piData, int *piSum )
{
int iCount ;
int iSumTmp; // 存储“和”的临时变量
iSumTmp = 0;
for (iCount = 0; iCount < iNum; iCount++)
{
iSumTmp += piData[iCount];
}
*piSum = iSumTmp;
}
反例:
void SumData(int iNum, int *piData, int *piSum )
{
int iCount;
*piSum = 0;
for (iCount = 0; iCount < iNum; iCount++ )
{
*piSum += piData[iCount]; // piSum成了工作变量,不好。
}
}


说明:带有“记忆”功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某种“记忆状态”。这样的函数既不易理解又不利于测试和维护。在C/C++语言中,函数的static局部变量是函数的“记忆”存储器。建议尽量少用static局部变量,除非必需。
说明:多用途的函数往往通过在输入参数中有一个控制参数,根据不同的控制参数产生不同的功能。这种方式增加了函数之间的控制耦合性,而且在函数调用的时候,调用相同的一个函数却产生不同的效果,降低了代码的可读性,也不利于代码调试和维护。
正例:
以下两个函数功能清晰:
int Add(int iParaOne, int iParaTwo)
{
return (iParaOne + iParaTwo);
}
int Sub(int iParaOne, int iParaTwo)
{
return (iParaOne – iParaTwo);
}
反例:
如果把这两个函数合并在一个函数中,通过控制参数决定结果,不可取。
int AddOrSub(int iParaOne, int iParaTwo, unsigned char ucAddOrSubFlg)
{
if (INTEGER_ADD == ucAddOrSubFlg) // 参数标记为“求和”
{
return (iParaOne + iParaTwo);
}
else
{
return (iParaOne –iParaTwo);
}
}
〖建议7-3-1〗尽量避免函数带有“记忆”功能。函数的输出应该具有可预测性,即相同的输入应当产生相同的输出。
〖建议7-3-2〗函数的功能要单一,不要设计多用途的函数。
〖建议7-3-3〗函数功能明确,防止把没有关联的语句放到一个函数中。
说明:防止函数或过程内出现随机内聚。随机内聚是指将没有关联或关联很弱的语句放到


同一个函数或过程中。随机内聚给函数或过程的维护、测试及以后的升级等造成了不便,同时也使函数或过程的功能不明确。使用随机内聚函数,常常容易出现在一种应用场合需要改进此函数,而另一种应用场合又不允许这种改进,从而陷入困境。
正例:
矩形的长、宽与点的坐标基本没有任何关系,应该在不同的函数中实现。
void InitRect(void)
{
// 初始化矩形的长与宽
tRect.wLength = 0;
tRect.wWidth = 0;
}
void InitPoint(void)
{
// 初始化“点”的坐标
tPoint.wX = 10;
tPoint.wY = 10;
}
反例:
矩形的长、宽与点的坐标基本没有任何关系,故以下函数是随机内聚。
void InitVar(void)
{
// 初始化矩形的长与宽
tRect.wLength = 0;
tRect.wWidth = 0;
// 初始化“点”的坐标
tPoint.wX = 10;
tPoint.wY = 10;
}
说明:冗长的函数无论从可读性和调试的角度来说都不受欢迎。
〖建议7-3-4〗函数体的规模要小,尽量控制在200行代码之内。
〖建议7-3-5〗为简单功能编写函数。
说明:虽然为仅用一两行就可完成的功能去编函数好象没有必要,但用函数可使功能明确化,增加程序可读性,亦可方便维护、测试。
正例:
改为如下就很清晰了。
int Max(int iParaOne, int iParaTwo)


{
int iMaxValue;
iMaxValue = (iParaOne > iParaTwo) ? iParaOne : iParaTwo;
return iMaxValue;
}
反例:
如下语句的功能不很明显。
iMaxValue = (iParaOne > iParaTwo) ? iParaOne : iParaTwo;
7.4 函数调用
【规则7-4-1】必须对所调用函数的错误返回值进行处理。
说明:函数返回错误,往往是因为输入的参数不合法,或者此时系统已经出现了异常。如果不对错误返回值进行必要的处理,会导致错误的扩大,甚至导致系统的崩溃。
正例:
在程序中定义了一个函数:
int DbAccess(WORD wEventNo, T_InPara *ptInParam, T_OutPara *ptOutParam);
在引用该函数的时候应该如下处理:
int iResult;
iResult = DbAccess(EV_GETRADIOCHANNEL, ptReq, ptAck);
switch (iResult)
{
case NO_CHANNEL: // 无可用无线资源
{
[异常处理]
break;
}
case CELL_NOTFOUND: // 小区未找到
{
[异常处理]
break;
}
default:
{
[其它处理]
}
}
[正常处理]
反例:
对上面的正例中定义的函数进行如下的处理就不合适。 


DbAccess(EV_GETRADIOCHANNEL, ptReq, ptAck);
[正常处理]
说明:递归调用特别是函数间的递归调用(如A->B->C->A),影响程序的可理解性;递归调用一般都占用较多的系统资源(如栈空间);递归调用对程序的测试有一定影响。故除非为某些算法或功能的实现方便,应减少没必要的递归调用。
对于前台软件为了系统的稳定性和可靠性,往往规定了进程的堆栈大小。如果采用了递归算法,收敛的条件又往往难以确定,很容易使得进程的堆栈溢出,破坏系统的正常运行;另外,由于无法确定递归的次数,降低了系统的稳定性和可靠性。
〖建议7-4-1〗减少函数本身或函数间的递归调用。
〖建议7-4-2〗设计高扇入、合理扇出的函数。
说明:扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数调用它。
扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,如总是1,表明函数的调用层次可能过多,这样不利程序阅读和函数结构的分析,并且程序运行时会对系统资源如堆栈空间等造成压力。函数较合理的扇出(调度函数除外)通常是3-5。扇出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函数进一步分解多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现的功能,也不能违背函数间的独立性。
扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。
较良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入到公共模块中。
8. 可靠性
为保证代码的可靠性,编程时请遵循如下基本原则:
◆正确性,指程序要实现设计要求的功能。
◆稳定性、安全性,指程序稳定、可靠、安全。
◆可测试性,指程序要具有良好的可测试性。
◆规范/可读性,指程序书写风格、命名规则等要符合规范。
◆全局效率,指软件系统的整体效率。
◆局部效率,指某个模块/子模块/函数的本身效率。
◆个人表达方式/个人方便性,指个人编程习惯。

你可能感兴趣的:(代码原则三)