软件的规范,在阅读和理解代码有很大的帮助。下面是收集的一些C语言软件规范。
一、文件与目录
1、文件命名
要准确清晰地表达其内容,同时文件名应该精炼,防止名字过长。
2、文件存储位置
软件包或逻辑组件的所有头文件和源文件都放在单独的同一目录下面,便于查找和简化一些编译工具的设置。
3、源文件格式
可选择32的hal驱动文件,作为自己的一个文件模版。
也可以按照如下的进行单排
- 文件头注释
- 防止重复引用头文件的设置
- #include 部分
- #define 部分
- enum 常量声明
- 类型声明和定义,包括 struct、union、typedef 等
- 全局变量声明
- 文件级变量声明
- 全局或文件级函数声明
- 函数实现。按函数声明的顺序排列
- 文件尾注释
注意:对文件的代码行数应避免过长。控制在1000行以内。
4、发布程序版本号
比如:V1.2.3.20230105_rc
说明:
第一位(1):主版本号。当功能模块有较大的变动,比如增加多个模块或者整体架构发生变化。此版本号由项目决定是否修改。
第二位(2):子版本号。当功能有一定的增加或变化,比如增加了对权限控制、增加自定义视图等功能。此版本号由项目决定是否修改。
第三位(3):修订版本号。一般是 Bug 修复或是一些小的变动,要经常发布修订版,时间间隔不限,修复一个严重的bug即可发布一个修订版。此版本号由项目经理决定是否修改。
日期版本号(20201228):用于记录修改项目的当前日期,每天对项目的修改都需要更改日期版本号。此版本号由开发人员决定是否修改。
希腊字母版本号(rc):此版本号用于标注当前版本的软件处于哪个开发阶段,当软件进入到另一个阶段时需要修改此版本号。此版本号由项目决定是否修改。
软件版本阶段说明:
Base版: 此版本表示该软件仅仅是一个假页面链接,通常包括所有的功能和页面布局,但是页面中的功能都没有做完整的实现,只是做为整体网站的一个基础架构。
Alpha版: 此版本表示该软件在此阶段主要是以实现软件功能为主,通常只在软件开发者内部交流,一般而言,该版本软件的Bug较多,需要继续修改。
Beta版: 该版本相对于α版已有了很大的改进,消除了严重的错误,但还是存在着一些缺陷,需要经过多次测试来进一步消除,此版本主要的修改对像是软件的UI。
RC版: 该版本已经相当成熟了,基本上不存在导致错误的BUG,与即将发行的正式版相差无几。
Release版: 该版本意味“最终版本”,在前面版本的一系列测试版之后,终归会有一个正式版本,是最终交付用户使用的一个版本。该版本有时也称为标准版。一般情况下,Release不会以单词形式出现在软件封面上,取而代之的是符号(R)。
当然还有一些大家经常在某些软件上看到版本所包含的英文词语,如果看不懂就比较尴尬了,但有一个测试版和一个专业版摆在面前,却因为不太了解这些常规命名而选错,下面大致了解下:
-
standard:标准版
-
full version:完整版,即正式版
-
lts:长期维护版本
-
ultimate:旗舰版
-
alpha:内部版本
-
beta:测试版
-
demo:演示版
-
enhance:增强版
-
free:自由版
-
lts:长期维护版本
-
release:发行版
-
rc:即将作为正式版发布
-
standard:标准版
-
upgrade:升级版
二、排版
1、代码缩进
程序块采用缩进风格,缩进空格数为4个。
2、程序块之间、前后无联系的语句中间要空行
void DemoFunc1(void)
{
uint8_t i;
<---- 局部变量和语句间空一行
/* 功能块 1 */
for (i = 0; i < 10; i++)
{
...
}
<---- 不同的功能块间空一行
/* 功能块 2 */
for (i = 0; i < 20; i++)
{
...
}
}
<---- 不同的程序块间空一行
void DemoFunc2(void)
{
}
3、一行代码只写一条语句
4、关键词之间的空格
在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->),后不应加空格。
例子:
(1) 逗号、分号只在后面加空格。
int_32 a, b, c;
(2) 比较操作符,赋值操作符"=“、 “+=”,算术操作符”+“、”%“,逻辑操作符”&&“、”&“,位域操作符”<<“、”^"等双目操作符的前后加空格。
if (current_time >= MAX_TIME_VALUE)
a = b + c;
a *= 2;
a = b ^ 2;
(3) “!”、“~”、“++”、“–”、“&”(地址运算符)等单目操作符前后不加空格。
*p = 'a'; /* 内容操作"*"与内容之间 */
flag = !isEmpty; /* 非操作"!"与内容之间 */
p = &mem; /* 地址操作"&" 与内容之间 */
i++; /* "++","--"与内容之间 */
(4) “->”、"."前后不加空格。
p->id = pid; /* "->"指针前后不加空格 */
(5) if、for、while、switch 等与后面的括号间应加空格,使 if 等关键字更为突出、明显,函数名与其后的括号之间不加空格,以与保留字区别开。
if (a >= b && c > d)
三、注释
1、程序有效注释占20%
2、 文件开始部分
应该给出关于文件版权、内容简介、修改历史等项目的说明
3、函数信息
对于函数,在函数实现之前,应该给出和函数的实现相关的足够而精练的注释信息。内容包括本函数功能介绍,调用的变量、常量说明,形参说明,特别是全局、全程或静态变量(慎用静态变量),要求对其初值,调用后的预期值作详细的阐述。具体的书写格式和包含的各项内容请参见如下的例子
/**
* @brief 向读卡器发命令,如果读卡器进入休眠,则首先唤醒它
* @param[in] 全局变量 gaTxCard[]存放待发的数据
* @retval None.
*/
4、边写代码边注释
修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除
5、注释内容要清楚明了
用词含义准确,防止注释二义性。错误的注释不但无益反而有害。注释主要阐述代码做了什么(What),或者如果有必要的话,阐述为什么要这么做(Why),注释并不是用来阐述它究竟是如何实现算法(How)的。
6、注释格式尽量统一
建议使用“/* …… */”,因为 C++注释“//”并不被所有 C 编译器支持
四、可读性
1、运算符优先级
在一个条件语句里面包含多个不同运算法,应该使用括号明确表达。避免歧义,或者与原设计不符。例子如下,
word = (high << 8) | low;
if ((a | b) && (a & c))
if ((a | b) < (c & d))
2、避免使用不易理解的数字
涉及物理状态或者含有物理意义的常量,不应直接使用数字,必须用有意义的枚举或宏来代替。
/* 错误示范 */
if (Trunk[index].trunk_state == 0) <---- 不规范的写法,应使用有意义的标识
{
Trunk[index].trunk_state = 1; <---- 不规范的写法,应使用有意义的标识
... /* program code */
}
/* 规范书写 */
enum trunk_state_e
{
TRUNK_IDLE = 0,
TRUNK_BUSY = 1
};
if (Trunk[index].trunk_state == TRUNK_IDLE)
{
Trunk[index].trunk_state = TRUNK_BUSY;
... /* program code */
}
五、变量、结构、常量、宏
1、常用类型可进行重定义
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned long int uint32_t;
typedef signed char int8_t;
typedef signed short int16_t;
typedef signed long int int32_t;
#define __IO volatile
2、变量作用域前缀
为了清晰的标识变量的作用域,减少发生命名冲突,应该在变量类型前缀之前再加上表示变量作用域的前缀,并在变量类型前缀和变量作用域前缀之间用下划线‘-’隔开。
具体的规则如下:
(1)对于全局变量(global variable),在其名称前加“g”和变量类型符号前缀。
uint32_t g_ulParaWord;
uint8_t g_ucByte;
(2)对于静态变量(static variable),在其名称前加“s”和变量类型符号前缀。
static uint32_t s_ulParaWord;
static uint8_t s_ucByte;
(3)函数内部等局部变量前不加作用域前缀。
(4)对于常量,当可能发生作用域和名字冲突问题时,以上几条规则对于常量同样适用。注意,虽然常量名的核心部分全部大写,但此时常量的前缀仍然用小写字母,以保持前缀的一致性。
(5)不要使用单字节命名变量,但是允许使用 i, j, k 这样的作为局部循环变量。
3、常量、宏、模版命名
这些应该全部大写。如果这些名字由多个单词组成,则单词之间用下划线分隔,并遵循 “属什么 _ 是什么 _ 做什么” 的命名形式。宏指所有用宏形式定义的名字,包括常量类和函数类;常量也包括枚举中的常量成员。
六、函数
1、函数命名
每一个函数名前缀需包含模块名,模块名为小写,与函数名区别开。如:uartReceive(串口接收),简单程序可不加模块名。
2、一个函数仅完成一件功能。
3、函数名应准确描述函数功能
避免使用无意义或含义不清的动词为函数命名。使用动宾词组为执行某操作的函数命名。避免用含义不清的动词如process、handle等为函数命名,因为这些动词并没有说明要具体做什么。
4、检查函数所有参数输入的有效性
如果约定由调用方检查参数输入,则应使用assert()之类的宏,来验证所有参数输入的有效性。
5、避免设计五个以上参数函数,不使用的参数从接口中去掉
目的减少函数间接口的复杂度,复杂的参数可以使用结构传递。尽量避免使用bool参数。
6、函数的返回值要清楚、明了
除非必要,最好不要把与函数返回值类型不同的变量,以编译系统默认的转换方式或强制的转换方式作为返回值返回。
7、减少函数本身或函数间的递归调用
说明:递归调用特别是函数间的递归调用(如A->B->C->A),影响程序的可理解性;递归调用一般都占用较多的系统资源(如栈空间);递归调用对程序的测试有一定影响。故除非为某些算法或功能的实现方便,应减少没必要的递归调用。
8、优化函数遵循原则
(1)能影响模块功能的实现。
(2)仔细考查模块或函数出错处理及模块的性能要求并进行完善。
(3)通过分解或合并函数来改进软件结构。
(4)考查函数的规模,过大的要进行分解。
(5)降低函数间接口的复杂度。
(6)不同层次的函数调用要有较合理的扇入、扇出。
(7)函数功能应可预测。
(8)提高函数内聚。(单一功能的函数内聚最高)
说明:对初步划分后的函数结构应进行改进、优化,使之更为合理。