1.简介

代码编写规则应该在建立在一个工程项目之前。该规则应该贯穿整个项目的始终以保证代码的一致性。采用标准的代码编写惯例,可大大简化项目的维护负担。在C语言中可以有多种代码的编写方法(当然其它编程序语言亦如此),你可以尽可能采用一种好的风格,以达到以下目的:

可移植 (Portability)

连贯 (Consistency)

整洁(Neatness)

易于维护(Easy Maintenance)

易于理解(Easy Understanding)

简洁(Simplicity)

不管你采用那种风格,我所强调的就是:这种风格一定要贯穿你项目的始终。在以后的内容中我还要提到:即使在一个团队合作的大型项目中,这种风格也要贯穿始终。采用通用的代码编写风格可以减轻代码维护的工作量并降低维护费用;这种通用的代码风格还可以避免重写代码。

2.排版

不同的编辑工具, TAB 键所设置的空格数目需要统一改为四格,最好将TAB键设置为4个空格,Makefile中由于语法需要TAB键,输入shift+TAB即可。

(1) 关键字if, while, for与其后的控制表达式的(括号之间插入一个空格分隔,但括号内的表达式应紧贴括号。例如:

while (1);

(2) 双目运算符的两侧插入一个空格分隔,单目运算符和操作数之间不加空格,例

如i = i + 1、 ++i、 !(i < 1)、 -x、 &a[1]等。

(3) 后缀运算符和操作数之间也不加空格,例如取结构体成员s.a、函数调用foo(arg1)、取数组成员a[i]。

(4) ,号和;号之后要加空格,这是英文的书写习惯,例如

for (i = 1; i < 10; i++)、 foo(arg1, arg2)。

(5) 代码中每个逻辑段落之间应该用一个空行分隔开。例如每个函数定义之间应该插入一个空行,头文件、全局变量定义和函数定义之间也应该插入空行。

(6) 一个函数的语句列表如果很长,也可以根据相关性分成若干组,用空行分隔,这条规定不是严格要求,一般变量定义语句组成一组,后面要加空行,return之前要加空行。

3.注释

3.1语句注释

(1) 单行注释应采用/* comment */的形式,用空格把界定符和文字分开。多行注释最常见的是这种形式:

/** Multi-line* Comment*/


3.2源文件顶部注释

顶头写不缩进,第二行80个*,作为参照作用,单行注释不要超过这个长度。Version表示当前版本,每次修改都需要修改version,在Journal添加上修改日志。自己新建的源文件注释推荐用中文,若是修改跟文本其他注释保持一致。

/*
********************************************************************************
* Copyright (C),1975-2020, Microsoft Co., Ltd.
* File Name : gpio_keys.c
* Author : dongxiang
* Version : V1.0
* Description : General driver template, if wanting to use it, you can use
* global case matching to replace DRIEVER_CASE and driver_case
* with your custom driver name.
* Journal : 2020-05-09 init v1.0 by dongxiang
* Others :
********************************************************************************
*/


3.3函数注释

说明此函数的功能、参数、返回值意义等,写在函数定义上侧,和此函数定

义之间不留空行,顶头写不缩进。

/*
* @description : Open the equipment
* @param - inode : transmit inode
* @param - filp : The device file, the file descriptor
* @return : 0: success; other: fail
*/static int led_open(struct inode *inode, struct file *filp){if (!atomic_dec_and_test(&gpioled.lock)) {atomic_inc(&gpioled.lock); return -EBUSY; } filp->private_data = &gpioled; return 0;}


3.4相对独立的语句组注释

对这一组语句做特别说明,写在语句组上侧,和此语句组之间不留空行,与当前语句组的缩进一致。注意,说明语句组的注释一定要写在语句组上面,不能写在语句组下面 。

3.5代码行右侧的简短注释

对当前代码行做特别说明,一般为单行注释,和代码之间至少用一个空格隔开,一个源文件中所有的右侧注释最好能上下对齐。函数内的注释要尽可能少用。注释只是用来说明你的代码能做什么(比如函数接口定义),而不是说明怎样做的,只要代码写得足够清晰,怎样做是一目了然的,如果你需要用注释才能解释清楚,那就表示你的代码可读性很差,除非是特别需要提醒注意的地方才使用函数内注释。

3.6复杂的结构体注释

复杂的结构体定义比函数更需要注释 。取linux中fs.h的一段结构体代码注释:

/*
* This is the Inode Attributes structure, used for notify_change(). It
* uses the above definitions as flags, to know which values have changed.
* Also, in this manner, a Filesystem can look at only the values it cares
* about. Basically, these are the attributes that the VFS layer can
* request to change from the FS layer.
*
* Derek Atkins <warlord@MIT.EDU> 94-10-20
*/struct iattr {unsigned int ia_valid; umode_t ia_mode; kuid_t ia_uid; kgid_t ia_gid; loff_t ia_size;struct timespec ia_atime;struct timespec ia_mtime;struct timespec ia_ctime;/*
* Not an attribute, but an auxiliary info for filesystems wanting to
* implement an ftruncate() like method. NOTE: filesystem should
* check for (ia_valid & ATTR_FILE), and not for (ia_file != NULL).
*/struct file *ia_file;};


3.7复杂宏定义和变量注释

取linux中fs.h的一段宏代码注释:

/*
* Maximum number of layers of fs stack. Needs to be limited to
* prevent kernel stack overflow
*/#define FILESYSTEM_MAX_STACK_DEPTH 2


4.命名

(1) 选用linux内核风格,变量、函数和类型名采用全小写和下划线组合。常量(宏定义和枚举常量)采用全大写和下划线组合。拒绝使用大小写组合和匈牙利风格。

(2) 全局变量和全局函数命名要详细。尽量避免使用全局变量,对于多次使用的常量,用宏或者枚举代替

(3) 结构体变量命名时,杜绝使用typedef。typedef会将变量的结构体类型隐藏,导致代码可读性差。

(4) 标识符的命名要清晰明了,可以使用完整的单词和大家易于理解的缩写。短的单词可以通过去元音形成缩写,较长的单词可以取单词的头几个字母形成缩写,也可以采用大家基本认同的缩写。例如count写成cnt, block写成blk, length写成len, window写成win, message写成msg, temporary可以写成temp,也可以进一步写成tmp。

5.函数

每个函数都应该设计得尽可能简单,简单的函数才容易维护。应遵循以下原则:

(1) 实现一个函数只是为了做好一件事情,不要把函数设计成用途广泛、面面俱到的,这样的函数肯定会超长,而且往往不可重用,维护困难。

(2) 函数内部的缩进层次不宜过多,一般以少于4层为宜。如果缩进层次太多就说明设计得太复杂了,应该考虑分割成更小的函数来调用(这称为Helper Function)。

(3) 函数不要写得太长,建议在24行的标准终端上不超过两屏,太长会造成阅读困难,如果一个函数超过两屏就应该考虑分割函数了。[CodingStyle]中特别说明,如果一个函数在概念上是简单的,只是长度很长,这倒没关系。例如函数由一个大的switch组成,其中有非常多的case,这是可以的,因为各个case之间互不影响,整个函数的复杂度只等于其中一个case的复杂度,这种情况很常见,例如TCP协议的状态机实现。

(4) 执行函数就是执行一个动作,函数名要准确描述该函数的功能,例如 get_current、 radix_tree_insert。避免使用含义不清的动词作为函数名。

(5) 比较重要的函数定义上面必须加注释,说此函数的功能、参数、返回值、错误码等。

(6) 另一种度量函数复杂度的办法是看有多少个局部变量, 5到10个局部变量就已经很多了,局部变量再多就很难维护了,应该考虑分割函数。

(7) 减少函数本身或函数间的递归调用;函数的参数1-3个;函数体不能太长,一个函数完成一个功能;检查函数输入参数的有效性

(8) 函数的参数缺省值只能出现在函数的声明中,而不能出现在定义体中。有多个参数,参数只能从后向前缺省void Foo(int x, int y=0, int z=0);

(9) 防止将函数参数作为工作变量。将函数参数作为工作变量,有可能错误得到改变参数内容,故很危险。因此对于必须要修改的参数内容,可以先定义局部变量代替修改,最后将局部变量值赋予该参数。

(10) 单一的函数(内部不调用其他函数)功能应该是可预测的,即输入相同的参数,输出得到结果应该相同。意思就是此函数不要使用函数参数以为的变量,影响输出值。

(11) 避免设计多参数函数,不使用的参数要去除,方便于该函数的调用。

6.编程注意

(1) 多重循环将多次循环放在内层,减少CPU切入循环层的次数;多次循环体内要避免含判断语句,将循环语句置于判断语句的代码块内。

7.宏

(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))


(2) 宏所定义的多条表达式放在大括号中

示例:下面语句只有宏的第一句被执行。

#define INIT_RECT_VALUE(a, b) \
a = 0;\
b = 0;


正确使用方法:

#define INIT_RECT_VALUE(a, b) \
{\ a = 0;\ b = 0;\}


(3) 使用宏时不允许参数发生变化

示例:如下用法导致错误

#define SQUEARE( a ) ((a) * (a))int a = 5;int b;b = SQUEARE( a++ ); //结果:a = 7,在宏里执行了两次,但是在正文代码里只 看出来增加一次1,容易误导。


正确使用方法:

#define SQUEARE( a ) ((a) * (a))int b = SQUEARE( a );a++;  //结果:a = 6, 只执行一次


8.LOG打印

8.1当前位置定位打印

程序调试时,可采用函数log打印,最好加上宏控,加上当前所处的文件、行数及函数。对于linux中的printk已经添加好了宏控的

printk(TAG, "<%s:%d> %s Error:  not find key node! \r\n", __FILE__, __LINE__, __func__ );//这里的TAG值可选:#define KERN_EMERG        "<0>" /* system is unusable */#define KERN_ALERT        "<1>" /* action must be taken immediately */#define KERN_CRIT         "<2>" /* critical conditions */#define KERN_ERR          "<3>" /* error conditions */#define KERN_WARNING      "<4>" /* warning conditions */#define KERN_NOTICE       "<5>" /* normal but significant condition */#define KERN_INFO         "<6>" /* informational */#define KERN_DEBUG        "<7>" /* debug-level messages */


具体实现原理,这里不作过多说明。

如果是C语言里的调试输出,如下样式:

#define LOG_DEBUG_SUPPORTprintf("%s:%d: Entry %s", __FILE__, __LINE__, __func__);#endif


当然也可以将宏封装成新的函数,放到头文件,代码使用起来更加简洁。

9.未完待续ing……

持续修改,欢迎对此提出自己的金点子!