C语言编程规范
规范制定说明
本套C语言编程规范为提高代码质量、便于维护、协同编码、可移植等特点而编写。要求所有参与编码人员要严格遵循本编程规范。
参考文献:
- 华为C语言开发编程规范。
- 阿里巴巴Java开发手册。
【示例】 展示的代码片段,做参考使用。
【原则】 在编写程序时必须严格遵守的规范。
【建议】 在编写程序时需要认真参考的规范。
【粗体】 在规范中特别强调的内容。
标识符命名与定义
建议使用unix like风格给标识符命名,即单词用小写字母,每个单词直接用下划线“_”分割。
例如:text_mutex,test_one,kernel_text_address。
我们对标识符定义主要是为了让团队的代码看起来尽可能统一,有利于代码的后续阅读和修改。标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解。尽可能给出描述性名称,不要节约空间,让别人很快理解你的代码更重要。
【示例】
常见缩写:
buffer 可缩写为 buff
command 可缩写为 cmd
compare 可缩写为 cmp
error 可缩写为 err
initialize 可缩写为 init
maximum 可缩写为 max
message 可缩写为 msg
parameter 可缩写为 para
previous 可缩写为 prev
register 可缩写为 reg
temp 可缩写为 tmp
【示例】
好的命名:
int error_number;
int number_of_completed_connection;
不好的命名:
int n;
int n_comp_conns;
【原则】
- 除了常见的通用缩写以外,不得使用单词缩写,不得使用汉语拼音。
- 代码中的命名均不能以下划线开始,也不能以下划线符号结束。
- 禁止使用单字母命名变量,但允许且仅能定义i、j、k、l作为局部循环变量。
- 严禁使用未经初始化的变量作为右值。
- 全局变量应增加“g_”前缀,全局数组应增加“a_”前缀。
【建议】
- 尽量避免名字中出现数字编号,除非逻辑上的确需要编号。
- 重构/修改部分代码时,应保持和原有代码的命名风格一致。
- 常量和宏的定义应该全部大写,多个单词应使用“_”相隔。
- 变量初始化必须赋予初值。
函数
函数设计的精髓:编写整洁函数,同时把代码有效组织起来。
整洁函数要求:代码简单直接、不隐藏设计者的意图、用干净利落的抽象和直截了当的控制语句将函数有机组织起来。
说明:扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数调用它。
设计高扇入,合理扇出(小于7)的函数。扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,例如:总是1,表明函数的调用层次可能过多,这样不利于程序阅读和函数结构的分析,并且程序运行时会对系统资源如堆栈空间等造成压力。通常函数比较合理的扇出(调度函数除外)通常是3~5。扇出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函数进一步分解多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现的功能,也不能违背函数间的独立性。
扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。
较良
【示例】
static void calibrate_run_measurement_overhead(void)
{
u64 T0, T1, delta, min_delta = 1000000000ULL;
int i;
for (i = 0; i < 10; i++)
{
T0 = get_nsecs();
burn_nsecs(0);
T1 = get_nsecs();
delta = T1-T0;
min_delta = min(min_delta, delta);
}
run_measurement_overhead = min_delta;
printf("run measurement overhead: %Ld nsecs\n", min_delta);
}
【原则】
- 避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层。
- 避免函数过长,新增函数不超过50行(非空非注释行)。
- 设计高扇入,合理扇出(小于7)的函数。
- 废弃代码(没有被调用的函数和变量)要及时清除。
【建议】
- 一个函数仅完成一个功能。
- 重复代码应该尽可能提炼成函数。
- 函数的参数个数不超过5个。
关于注释
建议所有变量(包括全局变量)定义语句的上方使用单行注释加以阐述和说明此变量的作用,语言简洁、易懂。最好不要超过一行。
优秀的代码可以自我解释,不通过注释即可轻易读懂。优秀的代码不写注释也可轻易读懂,注释无法把糟糕的代码变好,需要很多注释来解释的代码往往存在坏味道,需要重构。
【示例】
注释不能消除代码的坏味道:
/* 判断m是否为素数*/
/* 返回值:: 是素数,: 不是素数*/
int p(int m)
{
int k = sqrt(m);
for (int i = 2; i <= k; i++)
if (m % i == 0)
break; /* 发现整除,表示m不为素数,结束遍历*/
/* 遍历中没有发现整除的情况,返回*/
if (i > k)
return 1;
/* 遍历中没有发现整除的情况,返回*/
else
return 0;
}
重构代码后,不需要注释:
int IsPrimeNumber(int num)
{
int sqrt_of_num = sqrt (num);
for (int i = 2; i <= sqrt_of_num; i++)
{
if (num % i == 0)
{
return FALSE;
}
}
return TRUE;
}
【示例】
功能说明:
/*
* lib/prio_tree.c - priority search tree
*
* Copyright (C) 2004, Rajesh Venkatasubramanian <vrajesh@umich.edu>
*
* This file is released under the GPL v2.
*
* Based on the radix priority search tree proposed by Edward M. McCreight
* SIAM Journal of Computing, vol. 14, no.2, pages 257-276, May 1985
*
* 02Feb2004 Initial version
*/
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/prio_tree.h>
单行注释:
/*
* Maximum heap_index that can be stored in a PST with index_bits bits
*/
static inline unsigned long prio_tree_maxindex(unsigned int bits)
{
return index_bits_to_maxindex[bits - 1];
}
【原则】
- 文件头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者姓名、工号、内容、功能说明、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。
- 如果模块或函数有重大重构或修改时应标明修改时间和修改内容。
- 注释的内容要清楚、明了,含义准确,防止注释二义性。
- 修改代码时,维护代码周边的所有注释,以保证注释与代码的一致性。不再有用的注释要删除。
- 函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、设计约束等。
- 全局变量要有较详细的注释,包括对其功能、取值范围以及存取时注意事项等的说明。
【建议】
- 对于每个模块应使用块注释在模块上方说明作用和功能,语言简洁、易懂。
- 建议不要在模块或函数内部加入过多的注释影响阅读。注释的作用是解释难以表达的抽象意图,而不是重复描述代码。
- 建议注释应放在其代码上方相邻位置或右方,不可放在下面。如放于上方则需与其上面的代码用空行隔开,且与下方代码缩进相同。
排版与格式
程序的可读性和可维护性除了编码好坏外,还要很重要的因素是编码时的排版与格式。应该注意排版风格一致、美观。
【示例】
int swiotlb_map_sg_attrs(struct device *hwdev, struct scatterlist *sgl, int nelems,
enum dma_data_direction dir, struct dma_attrs *attrs)
{
struct scatterlist *sg;
int i;
BUG_ON(dir == DMA_NONE);
for_each_sg(sgl, sg, nelems, i)
{
phys_addr_t paddr = sg_phys(sg);
dma_addr_t dev_addr = phys_to_dma(hwdev, paddr);
if (swiotlb_force || !dma_capable(hwdev, dev_addr, sg->length))
{
void *map = map_single(hwdev, sg_phys(sg),sg->length, dir);
if (!map)
{
/* Don't panic here, we expect map_sg users to do proper error handling. */
swiotlb_full(hwdev, sg->length, dir, 0);
swiotlb_unmap_sg_attrs(hwdev, sgl, i, dir,attrs);
sgl[0].dma_length = 0;
return 0;
}
sg->dma_address = swiotlb_virt_to_bus(hwdev, map);
}
else
{
sg->dma_address = dev_addr;
}
sg->dma_length = sg->length;
}
return nelems;
}
EXPORT_SYMBOL(swiotlb_map_sg_attrs);
【原则】
- 程序块应采用缩进风格编写,每级缩进为1个tab(1个tab设为4个空格)。
- if、for、do、while、case、switch、default等语句独占一行。
- 相对独立的程序块之间、变量说明之后必须加空行。
【建议】
- 多个短语句(包括赋值语句)不允许写在同一行内,即一行只写一条语句。
- 任何运算符左右必须加一个空格。
- 一条语句不能过长,如不能拆分需要分行写。一行到底多少字符换行比较合适,产品可以自行确定。
- 在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->),后不应加空格。
- 注释符(包括„/‟„//‟„/‟)与注释内容之间要用一个空格进行分隔。
- 所有花括号必须独占一行,if语句即使一条表达式也要加花括号。
程序效率
本章节后面所有的原则和建议,都应在不影响前述可读性等质量属性的前提下实施。不能一味地追求代码效率,而对软件的正确、简洁、可维护性、可靠性及可测性造成影响。
记住:让一个正确的程序更快速,比让一个足够快的程序正确,要容易得太多。大多数时候,不要把注意力集中在如何使代码更快上,应首先关注让代码尽可能地清晰易读和更可靠。
【示例】
int foo()
{
if (异常条件)
{
异常处理;
return ERR_CODE_1;
}
if (异常条件)
{
异常处理;
return ERR_CODE_2;
}
正常处理;
return SUCCESS;
}
上面的示例代码看起来很清晰,而且也避免了大量的if else嵌套。但是从性能的角度来看,应该把执行概率较大的分支放在前面处理,由于正常情况下的执行概率更大,若首先考虑性能。应如下书写:
int foo()
{
if (满足条件)
{
正常处理;
return SUCCESS;
}
else if (概率比较大的异常条件)
{
异常处理;
return ERR_CODE_1;
}
else
{
异常处理;
return ERR_CODE_2;
}
}
除非证明foo函数是性能瓶颈,否则按照本规则,应优先选用前面一种写法。
【原则】
- 通过对数据结构、程序算法的优化来提高效率。
- 将不变条件的计算移到循环体外。