技术人员设计程序的首要目的是用于技术人员沟通和交流,其次才是用于机器执行。程序的生命力在于用户使用,程序的成长在于后期的维护及根据用户需求更新和升级功能。如果你的程序只能由你来维护,当你离开这个程序时,你的程序也和你一起离开了,这将给公司和后来接手的技术人员带来巨大的痛苦和损失。因此,为了程序可读、易理解、好维护,你的程序需要遵守一定的规范,你的程序需要设计。
“程序必须为阅读它的人而编写,只是顺便用于机器执行。”
――Harold Abelson 和 Gerald Jay Sussman
“编写程序应该以人为本,计算机第二。”
――Steve McConnell
为提高产品代码质量,指导仪表嵌入式软件开发人员编写出简洁、可维护、可靠、可测试、高效、可移植的代码,编写了本规范。
本规范将分为完整版和精简版,完整版将包括更多的样例、规范的解释以及参考材料(what & why),而精简版将只包含规则部分(what)以便查阅。
在本规范的最后,列出了一些业界比较优秀的编程规范,作为延伸阅读参考材料。
本规范主要包含以下两个方面的内容:
一:为形成统一编程规范,从编码形式角度出发,本规范对标示符命名、格式与排版、注释等方面进行了详细阐述。
二:为编写出高质量嵌入式软件,从嵌入式软件安全及可靠性出发,本规范对由于C语言标准、C语言本身、C编译器及个人理解导致的潜在危险进行说明及规避。
本规范适用于济南金钟电子衡器股份有限公司仪表台秤产品部嵌入式软件的开发,也对其他嵌入式软件开发起一定的指导作用。
原则:编程时必须坚持的指导思想。
规则:编程时需要遵循的约定,分为强制和建议(强制是必须遵守的,建议是一般情况下需要遵守,但没有强制性)。
说明:对原则/规则进行必要的解释。
实例:对此原则/规则从正、反两个方面给出例子。
材料:扩展、延伸的阅读材料。
Unspecified:未详细说明的行为,这些是必须成功编译的语言结构,但关于结构的行为,编译器的编写者有某些自由。例如C语言中的“运算次序”问题。这样的问题有 22 个。 在某种方式上完全相信编译器的行为是不明智的。编译器的行为甚至不会在所有可能的结构中都是一致的。
Undefined:未定义行为,这些是本质的编程错误,但编译器的编写者不一定为此给出错误信息。相应的例子是无效参数传递给函数,或函数的参数与定义时的参数不匹配。从安全性角度这是特别重要的问题,因为它们代表了那些不一定能被编译器捕捉到的错误。
Implementation-defined:实现定义的行为,这有些类似于“unspecified ”问题,其主要区别在于编译器要提供一致的行为并记录成文档。换句话说,不同的编译器之间功能可能会有不同,使得代码不具有可移植性,但在任一编译器内,行为应当是良好定义的。比如用在一个正整数和一个负整数上的整除运算“/ ”和求模运算符“% ”。存在76个这样的问题。从安全性角度,假如编译器完全地记录了它的方法并坚持它的实现,那么它可能不是那样至关重要。尽可能的情况下要避免这些问题。
声明(declaration):指定了一个变量的标识符,用来描述变量的类型,是类型还是对象,
者函数等。声明,用于编译器(compiler)识别变量名所引用的实体。以下这些就是声明:
extern int bar;
extern int g(int,int);
double f(int,double);[对于函数声明,extern关键字是可以省略的。]
定义(definition):是对声明的实现或者实例化。连接器(linker)需要它(定义)来引用内存实体。与上面的声明相应的定义如下:
int bar;
int g(int lhs,int rhs) {returnlhs*rhs;}
double f(int i,double d) {returni+d;}
规则/原则<序号>(规则类型):规则内容。
[原始参考]
<序号>:每条规则都有一个序号,序号是按照章节目录-**的形式,从数字1开始。例如,若在此章节有个规则的话,序号为0.5-1。
(规则类型):或者是‘强制’,或者是‘建议’。
规则内容:此条规则的具体内容。
[原始参考]:指示了产生本条款或本组条款的可应用的主要来源。
规则1.1-1(强制):标识符(内部的和外部的)的有效字符不能多于31。
[UndefinedImplementation-defined]
说明:ISO 标准要求在内部标识符之间前31 个字符必须是不同的,外部标识符之间前6 个字符必须是不同的(忽略大小写)以保证可移植性。我们这里放宽了此要求,要求内部、外部标示符的有效字符不能多于31即可。这样主要是便于编译器识别,代码清晰易读,并保证可移植性。
规则1.1-2(强制):具有内部作用域的标识符不应使用与具有外部作用域的标识符相同的
名称,在内部作用域里具有内部标示符会隐藏外部标识符。
说明:外部作用域和内部作用域的定义如下。文件范围内的标识符可以看做是具有最外部
(outermost )的作用域;块范围内的标识符看做是具有更内部(more inner)的作用域,连续嵌套的块,其作用域更深入。如果内部作用域标示符和外部作用域标示符同名,内部作用域标示符会覆盖外部作用域标示符,导致程序混乱。
实例:
INT8U test;
{
INT8U test; /*定义了两个test */
test = 3; /*这将产生混淆 */
}
规则1.1-3(建议):具有静态存储期的对象或函数标识符不能重用。
说明:不管作用域如何,具有静态存储期的标识符都不应在系统内的所有源文件中重用。它包含带有外部链接的对象或函数,及带有静态存储类标识符的任何对象或函数。在一个文件中存在一个具有内部链接的标识符,而在另外一个文件中存在着具有外部链接的相同名字的标识符,或者存在两个标示符相同的外部标示符。对用户来说,这有可能导致混淆。
实例:
test1.c
/**定义了一个静态文件域变量test1*/
static INT8U test1;
void test_fun(void)
{
INT8U test1; /*定义了一个同名的局部变量test1*/
}
test2.c
/**在另一个文件又定义了一个具有外部链接的文件域变量test1*/
INT8U test1;
原则1.1-4(强制):标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解。
说明:标示符的命名尽量做到见名知意,尽量让别人快速理解你的代码。
实例:
好的命名方法:
INT8U debug_message;
INT16U err_num;
不好的命名方法:
INT8U dbmesg;
INT16U en;
原则1.1-5(强制):常见通用的单词缩写尽量统一,不得使用汉语拼音、英语混用。
说明:简短的单词可以使用略去‘元音’字母形成缩写,较长的单词可以使用音节首字母
者单词前几个字母形成缩写,针对大家公认的单词缩写要统一。对于特定的项目要使
用的专有缩写应该注明或者做统一说明。
实例:常见单词缩写表(建议):
单词 |
缩写 |
单词 |
缩写 |
argument |
arg |
buffer |
buf |
clock |
clk |
command |
cmd |
compare |
cmp |
configuration |
cfg |
device |
dev |
error |
err |
hexadecimal |
hex |
increment |
inc |
initialize |
init |
maximum |
max |
message |
msg |
minimum |
min |
parameter |
param |
previous |
prev |
register |
reg |
semaphore |
sem |
statistic |
stat |
synchronize |
syn |
temp |
tmp |
|
|
原则1.1-6(建议):用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。
实例:常见反义词表:
正义 |
反义 |
正义 |
反义 |
add |
remove |
begin |
end |
create |
destroy |
insert |
delete |
first |
last |
get |
release |
increment |
decrement |
put |
get |
add |
delete |
lock |
unlock |
open |
close |
min |
max |
old |
new |
start |
stop |
next |
previous |
source |
target |
show |
hide |
send |
receive |
source |
destination |
copy |
pase |
up |
down |
|
|
原则1.1-7(建议):标示符尽量避免使用数字编号,除非逻辑上需要。
实例:
#define DEBUG_0_MSG
#define DEBUG_1_MSG
应改为更有意义的定义:
#define DEBUG_WARN_MSG
#define DEBUG_ERR_MSG
材料:《代码大全第2版》(Steve McConnell 著 金戈/汤凌/陈硕/张菲 译 电子工业出版社
2006年3月)"第11章变量命的力量"。
规则1.2-1(强制):文件名使用小写字母。
说明:由于不同系统对文件名大小写处理不同,Windows不区分文件名大小写,而Linux区分。所以文件名命名均采用小写字母,多个单词之间可使用”_”分隔符。
实例:disp.h os_sem.c
规则1.2-2(建议):工程源码使用GB2312编码方式。
说明:程序里的注释可能会使用中文,GB2312是简体中文编码,大部分的编辑工具和集成IDE环境都支持GB2312编码,为避免中文乱码,建议使用GB2312对源码进行编码。若需要转换成其他编码格式,可使用文本编码转换工具进行转换。
规则1.2-3(强制):工程源码使用版本管理工具进行版本管理。
说明:程序一般需要大量更新、修正、维护工作,且有时需要多人合作。使用版本管理工具可以帮助你提高工作效率。建议使用“Git”版本管理工具。
原则1.3-1(强制):变量命名应明确所代表的含义或者状态。
说明:变量名称可以使用名词表述清楚的尽量使用名词,使用名词无法描述清楚时,使用形
容词或者描述性的单词+名词的形式。变量一般为实体的属性、状态等信息,使用上
述方案一般可以解决变量名的命名问题,如果出现命名很困难或者无法给出合理的命
名方式时,问题可能出现在整体设计上,请重新审视设计。
规则1.3-2(强制):全局变量添加”G_”前缀,全局静态变量添加” S_ ”,局部静态变量添加”s_”前缀。使用大小写混合方式命名,大写字母用于分割不同单词。
说明:添加前缀的原因有两个。首先,使全局变量变得更醒目,提醒技术开发人员使用这些变量时要小心。其次,添加前缀使全局变量和静态变量变得和其他变量不一致,提醒技术开发人员尽量少用全局变量。
实例:
/**出错信息 */
INT8U G_ErrMsg;
/**每秒钟转动圈数 */
static INT32U S_CirclePerSec;
规则1.3-3(强制):局部变量使用小写字母,若标示符比较复杂,使用’_’分隔符。
说明:局部变量全部使用小写字母,和全局变量有明显区分,使读者看到标示符就知道是何
种作用域的变量。
实例:
INT32U download_program_address;
规则1.3-4(强制):定义指针变量*紧挨变量名,全局指针变量使用大写P前缀”P_”,局部指针变量使用小写p前缀”p _”。
实例: INT8U *P_MsgAddress; /*全局变量*/
INT8U *p_msg; /*局部变量*/
原则1.4-1(强制):函数命名应该明确针对什么对象做出了什么操作。
说明:函数的功能是获取、修改实体的属性、状态等,采用“动词+名词”的方式可以满足上述需求,若出现使用此方式命名函数很困难或不能命名的情况,问题可能出现在整体设计上,请重新审视设计方案。
规则1.4-2(强制):具有外部链接的函数命名使用大小写混合的方式,首字母大写,用于分割不同单词。
说明:函数具有外部链接属性的含义是函数通过头文件对外声明后,对其他文件或模块来
说是可见的。如果一个函数要在其他模块或者文件中使用,需要在头文件中声明该函
数。另外,在头文件声明函数,还可以促使编译器检查函数声明和调用的一致性。
实例:
char *GetErrMsg(ErrMsg *msg);
规则1.4-3(强制):具有文件内部链接属性的函数命名使用小写字母,使用’_’分隔符分割不同单词,且使用static关键字限制函数作用域。
说明:函数具有内部链接属性的含义是函数只能在模块或文件内部调用,对文件或模块外来
说是不可见的。如果一个函数仅在模块内部或者文件内部使用,需要限制函数链接围,
使用static修饰符修饰函数,使其只具有内部链接属性。在源文件中声明一遍具有内
部链接的函数同样具有促使编译器检查函数声明和调用的一致性。
实例:
static char get_key(void);
规则1.4-4(强制):函数参数使用小写字母,各单词之间使用“_”分割,尽量保持参数顺序从左到右为:输入、修改、输出。
说明:函数参数顺序为需输入参数值(这个值一般不修改,若不需要修改使用const关键字
修饰),需修改的参数(这个参数输入后用于提供数据,函数内部可以修改此参数),
输出参数(这个参数是函数输出值)。
规则1.5-1(强制):常量(#define定义的常量、枚举、const定义的常量)的定义使用全大写字母,单词之间加 ’_’分割的命名方式。
实例:
#define PI_ROUNDED 3.14
const double PI_ROUNDED = 3.14;
enum weekday{ SUN,MON,TUE,WED,THU,FRI,SAT };
规则1.5-2(建议):常数宏定义时,十六进制数的表示方法为0xFF。
说明:前面0x中的x小写,数据中的”A-F”大写。
规则1.6-1(强制):新定义类型名的命名应该明确抽象对象的含义,新类型名使用大写字母,单词之间加’_’分割,新类型指针在类型名前增加前缀”P_”。成员变量标示符前加类型名称前缀,首字母大写用于区分各个单词。
实例:typedef struct _STUDENT
{
StudentName;
StudentAge ;
......
}STUDENT , *P_ STUDENT;/* STUDENT 为新类型名称,P_ STUDENT 为新类型指针名*/
规则2.1.1-1(强制):头文件排版内容依次为包含的头文件、宏定义、类型定义、声明变量、声明函数。且各个种类的内容间空三行。
说明:头文件是模块对外的公用接口。在头文件中定义的宏,可以被其他模块引用。Project
中不建议使用全部变量,若使用则需在头文件里对外声明。模块对外的函数接口在模
块头文件里声明。
规则2.1.2-1(强制):源文件排版内容依次为包含的头文件、宏定义、具有外部链接属性的全局变量定义、模块内部使用的static变量、具有内部链接的函数声明、函数实现代码。且各个种类的内容间空三行。
说明:模块内部定义的宏,只能在该模块内部使用。只在模块内部使用的函数,需在源码文
件中声明,用于促使编译器检查函数声明和调用的一致性。
规则2.1.2-2(强制):程序块采用缩进风格编写,每级缩进4个空格。
说明:当前主流IDE都支持Tab缩进,使用Tab缩进需要打开和设置相关选项。宏定义、编译开关、条件预处理语句可以顶格。
规则2.1.2-3(强制):if、for、do、while、case、switch、defaul、typedef等语句独占一行,且这些关键字后需空一格。
说明:执行语句必须用缩进风格写,属于if、for、do、while、case、switch、default、typedef
等的下一个缩进级别。一般写if、for、do、while等语句都会有成对出现的{}