编程的总则: 编程首要是要考虑程序的可行性,然后是可读性、可移植性、健壮性以及可测试性。大多数程序员只是关注程序的可行性,而忽略了可读性,可移植性和健壮性,其实我个人认为,程序的可行性和健壮性与程序的可读性有很大的关系,能写出可读性很好的程序的程序员,他写的程序的可行性和健壮性必然不会差,也会有不错的可移植性.程序的可读性需要程序员有一个良好的编程风格.
好风格应该成为一种习惯。如果你在开始写代码时就关心风格问题,如果你花时间去审视和改进它,你将会逐渐养成一种好的编程习惯。一旦这种习惯变成自动的东西,你的潜意识就会帮你照料许多细节问题,甚至你在工作压力下写出的代码也会更好。
1.排版
a. 代码缩进空格数为4个。若是可能,尽量用空格来代替Tab键,因为有些编译器不支持Tab键(我自己至今未见过,但确实有这个风险),这给程序的移植带来了问题。在keil中这个问题很容易解决,只需在在keil主界面的菜单栏点击Edit—Configuration…,弹出Configuration窗口,点击Editor标签,在其中C/C++ File:、ASM、Other Files栏下,选中Insert spaces for tab:复选框,Tab对应的框中填4,这样按tab键就相当于按下四个空格键。
BOOL BufClr(UINT8 * dest,UINT32 size)
{
if(NULL ==dest || NULL==size)
{
return FALSE;
}
}
b. 较长的语句要分2行来书写,并用‘/’符号隔开。
uncrc=calcCRC16(Packet.p,unlen);
if((UINT8) uncrc != Packet.down_ser.mCrc[0] /
||(UINT8)(uncrc>>8)!= Packet.down_ser.mCrc[1])
{
BELL(ON);
}
c. 函数代码的参数过长,分多行来书写。
void UARTSendAndRecv(UINT8 *ucSendBuf,
UINT8 ucSendLength,
UINT8 *ucRecvBuf,
UINT8 ucRecvLength)
{
……
}
d. if、do、while、switch、for、case、default等关键字,必须加上大括号{}。
if(bSendEnd)
{
BELL(ON);
}
else
{
BELL(OFF);
}
//--------------------------
for(i=0; i< ucRecvLength; i++)
{
ucRecvBuf[i]=i;
}
//--------------------------
switch(ucintStatus)
{
case USB_INT_EP2_OUT:
{
USBCiEP2Send(USBMainBuf,ucrecvLen);
USBCiEP1Send(USBMainBuf,ucrecvLen);
}
break;
case USB_INT_EP2_IN:
{
USBCiWriteSingleCmd (CMD_UNLOCK_USB);
}
break;
……
}
2.注释
a. 边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。
注释应当准确、易懂,防止注释有二义性。错误的注释不但无益反而有害。
尽量避免在注释中使用缩写,特别是不常用缩写。
注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。
b. 说明性文件必选在文件头着重说明,例如*.c、*.h文件
/***************************************************************************
* 定时器+计数器测频
*
* 文 件: frequency.c
* 作 者: 小瓶盖
* 说 明:定时器+计数机测频率
* 编写时间: 2010.3.17
* 版 本:1.0
* 修改日期: 无
*---------------------------------------------------------------------------
* 注: 本程序定义6个数码管,经过实测,在200HZ~50KHZ时结果较准确,误差小于0.4%,
* 50KHZ以上频率未进行测量.据资料表明,可以测量到120KHZ,本程序未证明.
*****************************************************************************/
#include <xxxx.h>
void func(void)
{
}
c. 函数头应该进行注释,例如函数名称、输入参数、返回值、功能说明。
/**************将所有参数写入AT24C64,共4字节*********************
*说明:将表号和用户电量共四字节数据写入AT24C64中
*入口参数:
* 1.数据间接寻址地址-buf
* 2.写入到AT24C64的地址字-addh,addrl
* 3.写入字节数-count
*出口参数:1表示写成功,0表示写失败
***************************************************************/
bit write_byte(unsigned char * buf,
unsigned char addrh,
unsigned char addrl,
unsigned char count)
{
……
}
d. 全局变量要注释其功能,若为关键的局部变量同样需要注释其功能。
volatile UINT8 __ucSysMsg=SYS_IDLE;
void SYSSetMsgPriority(void)
{
SYSMSG Msgt;//临时存储消息
UINT8 i;
}
e. 复杂的宏定义同样要加上注释。
/* SYS_MSG_MAP 建立一个消息映射
宏参数NAME:消息映射表的名字
宏参数NUM_OF_MSG:消息映射的个数
*/
#define SYS_MSG_MAP(NAME,NUM_OF_MSG) do/
{/
DEFINE_MSG_NAME((NAME));/
UINT8 i;/
for(i=0;i< NUM_OF_MSG;i++)/
{/
ININ_CUR_MSG(i)/
}/
}while(0)
f. 复杂的结构体同样要加上注释。
/* 奇偶校验结构体*/
typedef struct _ PKT_PARITY
{
UINT8 m_ucHead1; //首部1
UINT8 m_ucHead2; //首部2
UINT8 m_ucOptCode; //操作码
UINT8 m_ucDataLength; //数据长度
UINT8 m_szDataBuf[16];//数据
UINT8 m_ucParity; //奇偶校验值
}PKT_PARITY;
g. 相对独立的语句组注释。对这一组语句做特别说明,写在语句组上侧,和此语句组之间不留空行,与当前语句组的缩进一致。注意,说明语句组的注释一定要写在语句组上面,不能写在语句组下面。
3.标识符
a. 变量的命名
方法一:采用匈牙利命名法。命名规则的主要思想是“在变量中加入前缀以增进人们对程序的理解”。
例如平时声明32位整型变量Length对应使用匈牙利命名法为unLength。现在列出经常用到的变量类型。
变量类型 示例
char cLength
unsigned char ucLength
short int sLength
unsigned short int usLength
int nLength
unsigned int unLength
char * szBuf
unsigned char * uszBuf
volatile unsigned char __ucLength
方法二:
Ø 局部变量以小写字母命名;
Ø 全局变量以首字母大写方式命名(骆驼式);
Ø 定义类型和宏定义常数以大写字母命名;
Ø 变量的作用域越大,它的名字所带有的信息就应该越多。
Ø 局部变量: int student_age;
Ø 全局变量: int StudentAge;
Ø 宏定义常数:#define STUDENT_NUM 10
Ø 类型定义: typedef INT16S int;
(我个人喜欢第二种方法)
b. 变量命名要注意缩写而且让人简单易懂,若是特别缩写要详细说明。
经常用到的缩写如:
Count 可缩写为Cnt
Message 可缩写为Msg
Packet 可缩写为Pkt
Temp 可缩写为Tmp
平时不经常用到的缩写,要注释:
SerialCommunication 可缩写为SrlComm //串口通信变量
SerialCommunicationStatus 可缩写为SrlCommStat //串口通信状态变量
c. 全局变量和全局函数的命名一定要详细,不惜多用几个单词,例如函数UARTPrintfStringForLCD,
因为它们在整个项目的许多源文件中都会用到,必须让使用者明确这个变量或函数是干什么用的。局部变量和只在一个源文件中调用的内部函数的命名可以。简略一些,但不能太短,不要使用单个字母做变量名,只有一个例外:用i、j 、k 做循环变量是可以的。
d. 用于编译开关的文件头,必须加上当前文件名称,防止编译时产生冲突。
例如在UARTInterface.h 头文件中,必须加上以下内容
#ifndef __UARTINTERFACE_H__
#define __UARTINTERFACE_H__
extern void UARTPrintfString(CONST INT8* str);
extern void UARTSendNBytes(UINT8 *ucSendBytes,UINT8 ucLen);
…… //其他外部声明的代码
#endif
e.禁止用汉语拼音作为标识符名称,可读性极差。呵呵。
f. 建议名称间的区别要显而易见。使用标识符名称要注意的一个相关问题是发生在名称之间只有一个字符或少数字符不同的情况,特别是名称比较长时,当名称间的区别很容易被误读时问题就比较显著,比如1(数字1)和l(L 的小写)、0 和O、2 和Z、5 和S,或者n 和h。
4.表达式和基本语句
a.不要编写太复杂的复合表达式;
例如:
i = a >= b && c < d && c + f <= g + h; //复合表达式过于复杂
b.不要有多用途的复合表达式;
例如:
d = (a = b + c) + r ; //应拆分为两个语句:
a = b + c;
d = a + r;
c.如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免使用默认的优先级。
例如:
if(a | b && a & c) //不良的风格
if((a | b) && (a & c)) //良好的风格
注意:只需记住加减运算的优先级低于乘除运算,其它地方一律加上括号。
d if 语句
d.a 布尔变量与零值比较
不可将布尔变量直接与TRUE、FALSE 或者1、0 进行比较。
根据布尔类型的语义,零值为“假”(记为FALSE),任何非零值都是“真”(记为TRUE)。TRUE的值究竟是什么并没有统一的标准。例如Visual C++ 将TRUE 定义为1,而Visual Basic 则将TRUE 定义为-1。
例:假设布尔变量名字为flag,它与零值比较的标准if 语句如下:
if (flag) // 表示flag为真时满足条件
if (!flag) // 表示flag为假时满足条件
其它的用法都属于不良风格,例如:
if (flag == TRUE)
if (flag == 1 )
if (flag == FALSE)
if (flag == 0)
d.b 整型变量与零值比较
应当将整型变量用“==”或“!=”直接与0比较。
例:假设整型变量为value,它与零值比较的标准if 语句如下:
if (value == 0)
if (value != 0)
不可模仿布尔变量的风格而写成
if (value) // 会让人误解 value 是布尔变量
if (!value)
小技巧:想必大家都有过将赋值操作符“=”当作比较相等操作符“==”用过,这个错误比较的隐晦,不易排查,而且编译器从不把这类事情当作是程序员犯下的错。避免的方法有两种,一种是养成良好的编程习惯,在比较数值时小心翼翼的处理;另一种方法见下面给出的代码:
if (NULL = = p)
{
……
}
是不是觉得这种书写方式很古怪?不是程序写错了?
当然不是!
有经验的程序员为了防止将 if (p = = NULL) 误写成 if (p = NULL),而有意把p 和NULL 颠倒。编译器认为 if (p = NULL) 是合法的,但是会指出 if (NULL = p)是错误的,因为NULL不能被赋值。所以,再次遇到判断整型变量是否与某个数相等时,请这样写吧:
if(2==flag)
{
……
}
d.c 浮点变量与零值比较
不可将浮点变量用“==”或“!=”与任何数字比较。
千万要留意,无论float 还是double 类型变量,都有精度限制。所以一定要避免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。
假设浮点变量的名字为x,应当将
if (x == 0.0) // 隐含错误的比较
转化为
if ((x>=-EPSINON) && (x<=EPSINON)) //EPSINON 是精度
5.杂项
a. 一些常量(如圆周率PI)或者常需要在调试时修改的参数最好用#define定义,但要注意宏定义只是简单的替换,因此有些括号不可少。
b. 不要轻易调用某些库函数,因为有些库函数代码很长(我是反对使用printf之类的库函数的,但是是一家之言,并不勉强各位)。
c. 对各运算符的优先级有所了解,记不得没关系,加括号就是,千万不要自作聪明说自己记得很牢。
d. 不管有没有无效分支,switch函数一定要defaut这个分支。一来让阅读者知道程序员并没有遗忘default,并且防止程序运行过程中出现的意外(健壮性)。
e. 函数的参数和返回值没有的话最好使用void。
f. 一些常数和表格之类的应该放到code中去以节省RAM。
g. 程序编完编译看有多少code多少data,注意不要使堆栈为难。
h. 减少函数本身或函数间的递归调用
i. 编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。
j. 在多重循环中,应将最忙的循环放在最内层
k. 避免循环体内含判断语句,应将循环语句置于判断语句的代码块之中。
l. 系统运行之初,要初始化有关变量及运行环境,防止未经初始化的变量被引用。
m. 编写代码时要注意随时保存,并定期备份,防止由于断电、硬盘损坏等原因造成代码丢失。