0.前言
“程序必须为阅读它的人而编写,只是顺便用于机器执行。” ——Harold Abelson 和 Gerald Jay Sussman
良好的编程风格使得代码具有更强的可读性,可维护性,以及可移植性。为了使自己在编码过程中能够保持良好的编程规范,形成自己的风格,我结合多篇著名编程规范文章以及自己工作中的经验,总结提炼,形成以下内容,以备自用与遵循。
1.头文件
原则1.1 头文件中适合放置接口的声明,不适合放置实现。
- 放置对外的声明:函数声明、宏定义、类型定义;
- 内部使用的函数、宏、枚举、结构体不应放入;
- 变量定义应放在.c文件中,尽量不要使用全局变量作为借口,即使必须使用,也应在.c文件中定义,在.h文件中声明(extern);
原则1.2 头文件应当职责单一。
- 头文件中不应包含其他的头文件,以避免复杂的依赖关系导致文件过大、编译时间过长;
原则1.3 头文件应向稳定的方向包含。
- 不稳定的头文件依赖于稳定的头文件,即不成熟的头文件包含成熟的头文件,产品依赖于平台,平台依赖于标准库。
规则1.1 每一个.c文件应有一个同名.h文件,用于声明需要对外公开的接口。
- 程序入口文件除外,如main函数所在文件;
- 对于内部使用函数,无需在.h中声明,建议使用static在.c文件头部声明, 如:static void fun(); ;
规则1.2 禁止头文件循环依赖。
规则1.3 .c/.h文件禁止包含用不到的头文件。
规则1.4 头文件应当自包含。
- 不包含其他头文件,能够独立编译;
规则1.5 总是编写内部#include保护符( #define 保护)。
- 所有头文件都应当使用#define 防止头文件被多重包含。命名格式应为:FILENAME_H,命名唯一,一般以"_"和”__"开头的标识符为系统保留或者标准库使用,自定义中不使用;
#ifndef FILENAME_H
#define FILENAME_H
..
#endif
规则1.6 禁止在头文件中定义变量。
**规则1.7 只能通过包含头文件的方式使用其他.c提供的接口,禁止在.c中通过extern的方式使用外部
函数接口、变量。**
规则1.8 禁止在extern "C"中包含头文件。
2.函数
原则2.1 一个函数仅完成一件功能。
原则2.2 重复代码应该尽可能提炼成函数。
规则2.1 避免函数过长,新增函数不超过50行(非空非注释行) 。
规则2.2 避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层。
**规则2.3 可重入函数应避免使用共享变量;若需要使用,则应通过互斥手段(关中断、信号量)对其
加以保护。**
- 可重入函数是指可能被多个任务并发调用的函数,共享变量指的是全局变量和static变量;
规则2.4 对参数的合法性检查
- 函数参数的校验交由函数自身校验,但此方式在函数嵌套较多时可能会带来校验代码冗余现象,因此对于非对外的函数的参数校验交由最上乘函数校验;
规则2.5 对函数的错误返回码要全面处理。
- 自写的函数或标准库函数都会有错误返回值,应对错误返回值进行全面的处理,以免程序出错,无法运行;
规则2.6 设计高扇入,合理扇出(小于7)的函数。
- 扇出是指一个函数直接调用(控制)其它函数的数目;
- 扇入是指有多少上级函数调用它;
规则2.7 废弃代码(没有被调用的函数和变量)要及时清除。
建议2.1 函数不变参数使用const。
- 函数中传入的参数不会被重新赋值的建议使用const,以便编译器检查,使代码更加牢固安全。
建议2.2 函数应避免使用全局变量、静态局部变量和I/O操作,不可避免的地方应集中使用。
建议2.3 检查函数所有非参数输入的有效性,如数据文件、公共变量等。
- 来自文件的数据,寄存器的数据需进行校验以免出错;
建议2.4 函数的参数个数不超过10个。
建议2.5 除打印类函数外,不要使用可变长参函数。
建议2.6 在源文件范围内声明和定义的所有函数,除非外部可见,否则应该增加static关键字。
3.标识符命名与定义
3.1 通用命名规则
- 无论变量还是函数,单词用小写字母,每个单词使用下划线“_”分割,如quit_sig,pthread_mutex_t,pthread_mutex_lock();;
- 对于全局变量应添加前缀 “g_”,如 g_number_of_cards;
- 对于局部静态变量,应添加前缀 “s_”,如 s_number_of_people;
- 对于宏的命名一律采用大写字母,并用“_”分割,如EXAMPLE_UNIT_TEST;
- 对于没枚举、和结构体的联合体等复合型的结构,在命名后添加后缀“_t”,如uint32_t,config_t;
原则3.1 标识符命名应该清晰明了,除通用缩写外,不使用用自定的缩写,不使用拼音。
- 通用缩写如下:
单词 | 缩写 |
---|---|
argument | arg |
argument | arg |
buffer | buff |
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 | para |
previous | prev |
register | reg |
semaphore | sem |
statistic | stat |
synchronize | sync |
temp | tmp |
建议3.1 使用正确的反义词组,与互斥名词、动词。
- 常见互斥词如下:
1 | 2 | 3 |
---|---|---|
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/paste | up/down |
建议3.2 尽量避免名字中出现数字编号,除非逻辑上的确需要编号。
3.2 文件命名规则
建议3.3 文件命名统一采用小写字符,单词之间使用下划线区分“_”。
3.3 变量命名规则
规则3.2 全局变量应该增加前缀“g_”。
规则3.3 静态变量应该增加前缀“s_”。
规则3.4 变量命名必须具有具体含义,但是对于局部循环变量,允许使用i,j,k,l,m,n。
3.4 函数命名规则
建议3.4 函数名采用小写字母,字母使用“_”分割。
3.5 宏的命名规则
规则3.5 宏以及枚举均使用全大写字母,以“_”分割单词。
规则3.6 除了头文件及宏开关,其他宏不可以“_”开头和结尾。
4.变量
原则4.1 一个变量只有一个功能。
原则4.2 结构功能单一。
- 设计数据结构时,不应一个结构中包含其他不相干内容
原则4.3 不用或者少用全局变量。
规则4.1 防止局部变量与全局变量同名。
规则4.2 使用结构时需要注意字节顺序。
规则4.3 严禁使用未经初始化的变量作为右值。
5.宏、常量
规则5.1 用宏定义表达式时,要使用完备的括号。
- 示例:如下定义的宏都存在一定的风险
#define RECTANGLE_AREA(a, b) a * b
#define RECTANGLE_AREA(a, b) (a * b)
#define RECTANGLE_AREA(a, b) (a) * (b)
正确的定义应为:
#define RECTANGLE_AREA(a, b) ((a) * (b))
规则5.2 将宏所定义的多条表达式放在do-while(0) 的大括号中。
-如下宏:
#define FOO(x) \
printf("arg is %d\n", x); \
do_something_useful(x);
若如下调用
eg1:
for (i = 0; i < 10; i++)
FOO(5);
eg2:
if (condition == true) {
FOO(1);
} else {
FOO(2);
}
则此时都会照成语法错误,但是使用do-while(0)方式就不会出现以上问题
#define FOO(x) do {\
printf("arg is %d\n", x); \
do_something_useful(x); \
} while (0)
规则5.3 使用宏时,不允许参数发生变化。
- 使用宏是参数不建议使用 ++、--等运算符,以免宏计算出的值偏离预期;
规则5.4 不允许直接使用魔鬼数字。
- 即不使用无意义的数字,以免阅读者无法理解该数字含义;
- 非必要时,不使用宏定义常量而使用const定义,预编译后宏定义的常量可能会变成魔鬼数字,不好定位调试。
6.注释
规则6.1 注释语法按Doxygen语法要求,以便使用该软件生成api文档
规则6.2 注释不是名词解释,而是说明用途
规则6.3 文件头注释
- 对于.c、.h、.asm文件,都必须在文件的开头对文件整体功能进行注释,注释使用模板如下:
/*!
*****************************************************************************
*
* Copyright © 2017-2018 Gustav Ou. All rights reserved.
*
* \file sample.c(可不写,软件会自动补充)
* \author Gustav
* \version 1.0
* \date 2017/10/22
* \brief 这是一个示例文件,向你展示文件头的模板
* 具体描述
*
*----------------------------------------------------------------------------
* \attention
* 这仅是一个示例文件
*
*****************************************************************************
* change history:
* <date> | <version> | <author> | <discription>
*----------------------------------------------------------------------------
* 2017/10/22 | 1.0 | Gustav | creat file
*----------------------------------------------------------------------------
*
*****************************************************************************
*/
规则6.4 函数头注释
- 函数头注释仅注释与函数声明前,如.h中的函数声明,.c中的内部函数声明
- 函数头模板(参数接收应上下对其,参数可选项使用中括号括起较多时可新起一行,参数应使用[in], [out], [in,out]
表示其方向):
/*!
* \brief 函数头测试模板
* 详细描述(根据需要,以下跟一个空行表示结束)
*
* \param [in] param1 参数一解释
* [0: 高级,1:中级,2: 低级,其他: 无级别 ]
* \param [out] param2 参数二解释
* \param [in] param3 参数三解释 [1: 有效,2: 无效]
*
* \retval returnvale 返回值的解释 [1: 成功,2: 失败](void无必要)
*/
规则6.5 注释块
- 详细注释
/*!
* ... text ...
*/
- 繁简并存的注释
/*! \brief Brief description.
* Brief description continued.
*
* Detailed description starts here.
*/
- 注释拆成单行
//! Brief description.
//! Detailed description
//! starts here.
或者
//! Brief description, which is
//! really a detailed description since it spans multiple lines.
/*! Another detailed description!
*/
规则6.6 成员注释
- 如果需要在文件的变量,结构体、联合体、类、枚举等的成员添加注释,需要使用<标记
int var; /*!< Detailed description after the member */
//!或
int var; //!< Brief description after the member
排版
规则7.1 程序块采用缩进风格编写, 每级缩进为4个空格。
规则7.2 相对独立的程序块之间、变量说明之后必须加空行。
规则7.3 一条语句不能过长,如不能拆分需要分行写,一行132列以内。
规则7.4 起始大括号放置在关键字(if,switch,for,while,do)同一行,结束大括号新起一行。
if (x is true) {
we do y
}
switch (action) {
case KOBJ_ADD:
return "add";
case KOBJ_REMOVE:
return "remove";
case KOBJ_CHANGE:
return "change";
default:
return NULL;
}
do {
body of do-loop
} while (condition);
if (x == y) {
..
} else if (x > y) {
...
} else {
....
}
规则7.5 大多数关键字、二元、三元运算符加空格
//!关键字后添加空格
if, switch, case, for, do, while
//!函数或类函数不加空格
s = sizeof(struct file);
//!二元、三元运算符后添加空格
= + - < > * / % | & ^ <= >= == != ? :
//!一元运算符后不加空格
& * + - ~ ! sizeof typeof alignof __attribute__ defined
//!前后缀自家自减运算符后不加空格
++ --
//!成员操作符后不加空格
. ->
//!逗号后添加空格
,
8.打印
- log打印分为多种情况,调试用打印,错误提示,警告提示等,建议将其独立成头文件引用:
#define DEBUG
#define ERROR
#define WARNING
#define INFO
#define NONE_MSG
#ifdef DEBUG
//#define DEBUG_MSG(format, args...) printf("DEBUG(%s;%d;%s): "format"\n", __FILE__, __LINE__, __FUNCTION__, ##args)
#define DEBUG_MSG(format, args...) printf("DEBUG(%s;%d): "format"\n", __FILE__, __LINE__, ##args)
#else
#define DEBUG_MSG(format, args...)
#endif
#ifdef ERROR
#define ERROR_MSG(format, args...) printf("ERROR: "format"\n", ##args)
#else
#define ERROR_MSG(format, args...)
#endif
#ifdef WARNING
#define WARNING_MSG(format, args...) printf("WARNING: "format"\n", ##args)
#else
#define WARNING_MSG(format, args...)
#endif
#ifdef INFO
#define INFO_MSG(format, args...) printf("INFO: "format"\n", ##args)
#else
#define INFO_MSG(format, args...)
#endif
#ifdef NONE_MSG
#define MSG(args...) printf(args)
#else
#define MSG(args...)
#endif
参考
提示
可能有人会觉得注释部分在实际编程中过于繁琐,若按照文中要求,可能会大大影响编程效率,不过不用担心,重复的事情我们总可以交给计算机来做,后续文章将会介绍如何使用SourceInsight的宏编程,来快速添加注释,生成文件头等。