程序书写规范
目录
1 总述
程序书写规范应建立于一项工程之初, 且该规范应贯穿整个工程之始终。 采用一些规范有助于提高劳动生产率, 并使工程软件便于维护。 好的书写风格无非是希望达到以下目的:
- 可移植性好
- 兼容性好
- 干净整齐
- 易于维护
- 易看懂
- 简明扼要
- 降低错误的产生
以下所总结的是日常工作中遇到的容易产生分歧的各种情况。我们力争制定出一个大家都能够遵守的书写规范。其中的某些规定或许不是最好的,但这样规定是有其充足的理由的。它们或许不符合您多年的编程习惯,但请适应它,因为,一个人是很容易做到使用两个以上的编程风格同时开发不同的工程。但是,它们并不是金科玉律,只是一个建议,却是一个强烈的建议。在遇到本文没有提到的情况时,可以根据实际情况和需要调整缩进。归根结底是为了更加清晰和正确地反映出代码的逻辑结构。 本文仅仅是在程序的代码格式层次进行了规定,这仅仅是冰山一角。还有大量的与代码逻辑结构涉及方面的问题,由于其内容过于零散和复杂,我们暂时无力总结,还请大家平时注意积累。
2 缩进
Tab长度为4,缩进长度为4。一律使用4个空格代替Tab符号进行缩进,不允许在代码中出现Tab符号。同一层次的缩进必须严格对齐。 花括号的左右括号需处在单独的一行,其所包含的语句要进行缩进。
3 代码宽度
为了便于代码的阅读,将代码的最大宽度为限定为100列。即每行代码的长度尽量不要超过100个字符,否则要对长语句换行。
4 代码格式
此处规定的代码格式是在ANSI和GNU风格的基础上进行的少量修改而形成的。
4.1 if-else语句
if (time.second < 50) { /*开花括号前后均要加换行符*/ time.second = 50; } else /*else关键字的前后都要加换行符*/ { TimeDecAddMinute(&time, 1); time.second = 50; }
4.2 switch-case语句
switch (expression) { case label1: /*switch中的case语句要进行缩进*/ break; /*case语句中的子语句要进行缩进*/ case label2: return 0; /*若在case与break语句之间出现了continue,return,goto等跳出 switch语句的命令,需使用//对break语句进行注释,不准删除 break语句*/ //break; ... default: /*必须存在default语句*/ break; }
4.3 while语句,do语句与for语句
while (TRUE) { /*在开花括号前后都加换行符*/ a = 1; } do { a = 1; } while (FALSE); /*while关键字与闭花括号处在同一行,在while关键字前后都要加空格*/ for (i = 0; i < 5; i++) { printf("%d\n", i); }
4.4 空格的添加
- 在分支语句的if,switch关键字和循环语句的while,for关键字后面添加一个空格。示例如上所示。
- 双元运算符与两个操作数之间至少有一个空格。双元运算符包括:
- 算数运算符: +, -, *, /, %。
- 逻辑运算符: ==, !=, &&, ||。
- 位操作符: , , &, |, ^。
例如:
a + b / 2 + c % 10 + 1 << 3
- 单元运算符与操作数之间不能有空格。单目运算符包括:
- 数组下标[], 函数调用(), 结构与联合的成员"."和->。
- 自增++, 自减–。
- 取地址符&和间接寻址符*。
- 按位取反~, 逻辑非!。
- 一元正号和一元负号+, -。
- 赋值符与变量和表达式之间至少有一个空格。赋值符包括: =, *=, /=, &=, +=, -=, =, =, &=, ^=, ^=, |=
- 在小括号与变量、表达式之间不准有空格。例如:
us = TimeSubTimeBcd(*start_time, task->CollectStartTime);
switch (part)
{
...
}
- 在逗号","前面不准加空格,后面必须添加一个空格。例如:
if (GPIO_ReadBit(key->GPIOx, key->Port_Pins)) { return; } LCD_DisplayPageArea((u8*)SignalPlans[GprsQuality], 16, 7, 112);
- for语句中的分号";"前面不能有空格,后面只能有一个空格。例如
for (i = 0; i < 10; ++i)
{
n += i;
}
- 注释符号和注释内容之间没有空格。
/*注释内容*//*comment*/
4.5 长语句的换行方式
当一条语句很长时,我们需要根据代码宽度(100列)对长语句进行换行。换行以后的部分,要相对于原语句缩进一部分。 在换行时,我们通常在一个变量或者常量之前换行,把逗号之类的分隔符、运算符留在前一行的行尾,相似的变量或运算符进行对齐,以保持美观。
/*这是一条很长的语句*/ myvar = myvar1 + myvar2 + myvar3 - myvar4 - myvar5 * myvar6 * myvar7 + myvar8 / myvar9 + myvar10 + myvar11 - myvar12 - myvar13 * myvar14 * myvar15 / myvar16; /*myvar1、myvar8和myvar15保持垂直对齐,"+"、"-"、"*"保持垂直对齐*//* 后面的语句恢复正常的缩进位置*/
函数调用和定义时, 如果参数个数很多, 或者要传递的表达式写起来很长,那么也会涉及到长语句换行问题,最基本的原则是:尽可能地在参数与参数之间换行,并将逗号保留在上一行行末:
/*在参数与参数之间换行*/ printf("This function call has many parameters. %d %d %d %d\n", myvar1, myvar2, myvar3, myvar4);
在一些必要的情况下,我们为了清晰地列出函数的每一个参数,可以采取类似结构声明的写法:
/*每个参数分行*/ printf("This function call has many parameters. %d %d %d %d\n", myvar1, myvar2, myvar3, myvar4 );
当传入的表达式较为复杂时,这种写法会显得格外地清晰。类似地,在同时声明多个同一类型的变量时,必须如此书写:
u16 myvar1; u16 myvar2; u16 myvar3; u16 myvar4;
它的主要目的是便于对单个变量进行注释,如:
u8 username[MAXN]; /*用户名*/u8 password[MAXN]; /*密码*/u8 desc[MAXN]; /*描述*/
对于较长表达式, 可以采用如下风格:
if ( /*圆括号缩进2个空格*/ (electricity_time->year != time.year) || (electricity_time->month != time.month) || (electricity_time->day != time.day) || /*类似于!=和||的运算符要添加额外空格实现上下对齐*/ ((electricity_time->hour / 3) != (time.hour / 3)) ) /*右圆括号在处在单独一行, 与其所匹配的左括号处在同一列*/ { memset(ElectricityGetBuf, 0xFF, PER_METER_SIZE); }
4.6 其他
- 即使分支和循环语句只包含一条语句,也要将该语句放到一对花括号中。例如
if (y != 0) { if (x != 0) { /*这样做既使代码清晰易读, 又减少了错误的发生(如将单语句改为复合语句却忘记将其放入花括号中)*/ result = x / y; } } else /*也避免了"悬空else"的问题*/ { printf("Error: y is equal to 0\n"); }
- 不推荐嵌套层数过多的条件语句。
/*不推荐*/if (x != 0) { while (y != 0) { if (z != 0) { for (i = 0; i < 10; i++) { fun3(i); } fun2(z); } fun1(y); } }
- 推荐变量在声明同时初始化。
- 建议一条声明语句只声明一个变量。不推荐具有相同类型的变量在一条声明语句中声明。严禁在一条声明语句中声明不同类型的变量。例如:
/*推荐如下声明方式*/u32 index_a; u32 index_b; u32 index_c; /*不推荐如下声明方式*/u8 arr1, arr2, arr3; /*严禁如下声明方式*/u8 i, *p1; /*i的类型是u8, p1的类型是u8* */u8* p1, i; /*p1的类型是u8*, i的类型是u8。有人可能错误地希望使用该语句声明类型为u8*的两个变量p1和i*/
- 严禁连续赋值。例如:
/*严禁*/ a = b = c;
- 相对独立的代码块之间使用空行分割。
- 不要吝惜你的圆括号。除了加减乘除外,建议使用圆括号分隔具有不同优先级的表达式。
- 行尾无空格,文件末尾至少保留一个空行。
5 注释
5.1 总述
- 注释尽量使用C风格的/* */, 也可以少量使用C++风格的//, 原则上规定使用/**/注释文字,使用//注释代码。
- 一定要为非临时的宏、全局变量和局部变量添加注释。
- 养成随时为编写的代码书写注释的习惯。
- 严禁仅仅自动生成了注释框架而不填写其中内容的行为。
- 在函数头部注释和文件头部注释中详细记录对代码的更改记录。
- 修改代码的同时不要忘记修改相应的注释。
- 注意在源文件和头文件中相同函数的头部注释保持一致。
5.2 文件头部注释
/*************************************************************************************************** * (c) Copyright 1992-2009 Embedded Products Research Center * All Rights Reserved * *\File usdl_xxx.h *\Description XXXXXXXXXXXXX *\Log 2008.XX.XX Ver 1.0 张三 * 创建文件。 ***************************************************************************************************/
注意:由于技术原因,在上例中每行的开始处添加了一个多余的空格,请在复制代码时手动删除。
- 注释:文件头注释必须位于文件第一行,不得添加规定项目外的任何文字。该注释作用文件包括:.c .h .s等项目中的手动编码文件。
- 框架:顶端、底端边框宽度为100个字符(包括:"*"、"/"),每行第一个字节用"*"占位,表示左边框。
- 注释分为注释命令和注释内容两部分,注释命令为File,Description等,以\开始,紧跟开头的*,所有注释内容要求按Tab对齐,即列号col=4*n+1, n是自然数。在这里n = 4。
- 版权:Copyright起始位置与每项内容对齐。
- 折行:每行长度不得超过注释框右边界,超过则折行。折行后,第N行起始位置不得左于注释内容的起始位置。"File"、版权项目不允许折行。
- \File命令:文件名、扩展名(小写)与源文件一致。
- \Description命令:文件描述。推荐填写模块名称+功能(如:XXX模块。用作XX。)。必须以全角句号结尾
- \Node:用于描述备注或标注的注解。该项中编号使用半角符号。编号暂限制为两级:一级编号使用数字(如:1、2、5),二级编号使用小写字母(如:a、b、c)。二级编号起始位置比一级编号右移两个字符。内容紧跟")"填写,折行后起始位置与上一行内容对齐、不得左于编号。必须以全角句号结尾。
- 在注释中的换行在生成的文档中均被忽略。若希望强制换行,有两种方法:1. 重新开始一个新的\Note命令;2. 使用\n命令。
- \Log:格式:日期+间隔+版本+间隔+作者+换行+备注。其中日期、版本、作者中的间隔使用四个空格;备注换行书写,起始位置第一行对齐。备注必须以全角句号结尾。日期须填写完整,使用半角"."分隔;版本格式为:"Ver"+一个空格+版本号,版本号中只允许存在一个"."。
- 全角:除注释命令、英文描述外,所有中文描述均使用全角(如:句号使用"。"、括号使用"(")。
- 注解:当某项存在过长或无法简捷表达的内容时,可以使用"(见 Note 1)"标注,并在\Note中添加注解。
- 修改:当代码发生重要改动后,须修改对应项,并在Log项中记录,同时适当更新版本(版本号可以跳增或不变)。
- 头文件:头文件可以描述为"XX模块接口文件"。
5.3 函数头部注释
/*************************************************************************************************** *\Function GenerateCRC16_IBM *\Description 根据以src起始长len的串生成16位CRC-IBM校验值 *\Parameter src 源数据 *\Parameter len 数据长度 *\Parameter seed 初始值 *\Return u16 *\Note CRC16-IBM = X16 + X15 + X2 + 1 *\Log 2008.09.19 Ver 1.0 xxxxxx * 创建函数。 ***************************************************************************************************/
注意:由于技术原因,在上例中每行的开始处添加了一个多余的空格,请在复制代码时手动删除。
- 注释:函数头注释与函数声明(或定义)之间不许添加空行。注释与上方代码至少预留一行。
- 框架:顶端、底端边框宽度为100个字符(包括:"*"、"/"),每行第一个字节用"*"占位,表示左边框。
- 注释分为注释命令和注释内容两部分,注释命令为File,Description等,以\开始,紧跟开头的*。所有注释内容的起始位置要求按Tab对齐,即列号col=4*n+1, n是自然数。
- 折行:每行长度不得超过注释框右边界,超过则折行。折行后,第N行起始位置不得左于第一行起始位置。
- \Function:函数名称,该项只填写函数名称,不包括返回值、参数等信息。
- \Description:函数功能描述。必须以全角句号结尾。
- \Parameter:一项只能填写一个参数。格式:参数名+空格+说明。参数不填写类型,参数说明起始位置按Tab对齐,具体见3)。若函数无参数则填写void。若说明不构成整句,可以不使用句号结尾。若说明不构成整句,可以不使用句号结尾。
- \Return:填写返回值类型及对返回值的描述,无返回值则填写"void"。
- \Note:用于描述备注或标注的注解。该项中编号使用半角符号。编号暂限制为两级:一级编号使用数字(如:1、2、5),二级编号使用小写字母(如:a、b、c)。二级编号起始位置比一级编号右移两个字符。内容紧跟")"填写,折行后起始位置与上一行内容对齐、不得左于编号。必须以全角句号结尾。
- 在注释中的换行在生成的文档中均被忽略。若希望强制换行,有两种方法:1. 重新开始一个新的\Note命令;2. 使用\n符号。
- \Log:格式:日期+间隔+版本+间隔+作者+换行+备注。其中日期、版本、作者中的间隔使用四个空格;备注换行书写,起始位置第一行对齐。备注必须以全角句号结尾。日期须填写完整,使用半角"."分隔;版本格式为:"Ver"+一个空格+版本号,版本号中只允许存在一个"."。
- 注解:当某项存在过长或无法简捷表达的内容时,可以使用"(见 Note 1)"标注,并在\Note中添加注解。
- 修改:当代码发生重要改动后,须修改对应项,并在Log项中记录,同时适当更新版本(版本号可以跳增或不变)。
- 头文件:头文件中的函数声明注释必须与函数定义注释相同。
5.4 代码分隔注释
/*************************************************************************************************** *公共宏定义块 起始 ***************************************************************************************************/ #define NUM_ONE 0x01 #define NUM_TWO 0x02 #define NUM_THREE 0x03 /*************************************************************************************************** *公共宏定义块 结束 ***************************************************************************************************/
- 作用:分隔带既可以用来将同一类代码包括成一块,也可以用来将代码分割成上下两区。
- 框架:顶端、底端边框宽度为100个字符(包括:"*"、"/"),描述行第一个字节用"*"占位,表示左边框。建议描述行为一行。
- 折行:每行长度不得超过注释框右边界,超过则折行。
- 描述:若描述内容构成整句则必须以全角句号结尾。
- 块分隔:若分隔带用来将同一类代码包括成一块,则必须明确表明块的上、下界(起始、结束分隔带)。建议上、下界两侧至少保留一个空行。
- 空分隔:如果不需要填写分隔描述,可以只保留顶端、底端边框。
5.5 代码块末尾注释
建议在占据较多行数语句的右花括号的后面添加注释标明其所结束的语句。如
/*初始化即时电量*/for ( i = 0; i < 4; i++ ) { MeterBlock->CurrentElectricity [index][i] = 0xFF; MeterBlock->CurrentElectricity1[index][i] = 0xFF; MeterBlock->CurrentElectricity2[index][i] = 0xFF; MeterBlock->CurrentElectricity3[index][i] = 0xFF; MeterBlock->CurrentElectricity4[index][i] = 0xFF; } /*end of for*/
6 命名规则
符号的命名要简短且有意义。尽量使用英文单词及其常用缩略语,不要使用汉语拼音及其缩写。
6.1 工程名
工程名是由以下划线"_"分隔的描述其属性的关键字组成, 其中的字母必须是大写形式。如DW708J_DW912CMAINB080605_旧居民规约。
6.2 工程目录结构
目录名以小写字母命名。 头文件要放到inc文件夹下, 源文件要放到src文件夹下。
6.3 文件的命名
文件名由小写字母和下划线组成, 使用下划线分隔文件名的不同组成部分,如app_meter_manage.c, app_device_manage.h等。
6.4 宏的定义
宏名一律全部使用大写字母,使用下划线连接多个单词: #define 标识符(x1, x2, …,xn) 替换列表 对于宏定义中的圆括号,有两条规则要遵循:
- 如果宏的替换列表中有运算符, 那么始终要将替换列表放在括号中,例如: #define TWO_MINUTE (60 * 2)
- 如果宏有参数, 每次参数在替换列表中出现时都要放在园括号中。 #define SCALE(x) ((x) * 10)
6.5 函数与全局变量的命名
以驼峰的形式命名,即每个单词的首字母要大写,单词之间紧密相连。如:
const u16 CrctableIBMRefl; void BitPointSet( void* src, u8 pos, u8 width ) { }
6.6 局部变量的命名
局部变量使用小写字母加下划线连接命令。局部变量的声明要统一放在函数的起始处,并对其初始化。 变量命名尽量简洁且有意义,且能够反映变量的类型,但也允许适度使用循环变量i、j、k以及临时变量tmp等进行命名。例如:
u8 i = 0; u8 temp = 0; u16 current_element = 0;
7 参考文献
- (美)K.N.King 著.C语言程序设计现代方法.C Programming: A modern Approach.吕秀锋 译
- 谈谈代码缩进. http://blog.csdn.net/richardbao2000/archive/2006/09/24/1270083.aspx
- 嵌入式实时操作系统uc/OS-II
- Doxygen Documents