《编码checklist规范》学习笔记
《编码checklist规范》
posts
- 《编码checklist规范》学习笔记
0 前言
在学习完《C陷阱与缺陷》后,发现了很多以前没有注意到的错误,比如:字符串与字符的区别,指针函数与函数指针的区别等等。《C陷阱与缺陷》是本值得收藏、反复观看的书籍。 本书《Checklist编码规范》与《C陷阱与缺陷》相辅相成,好的编码规范在编程中能减少很多你难以预料到的BUG,显著提高代码质量。文中加粗的部分是我自认为比较重要且以前没有注意的部分。
1 排版
1.0 总则
总则: 统一清晰的排版可以帮助代码阅读者迅速聚焦代码的关键逻辑,迅速定位区块的开始结束位置,大大提高代码阅读的效率。主要注意以下几点:
- 排版风格在同一文件中,必须保持统一;
- 尽量把关系密切的逻辑集中在一起,保证视线无需漂移即可浏览到整个逻辑单元;
- 在尚未养成习惯之前,可采用astyle之类的工具软件格式化代码;
1.1 缩进
代码的缩进可以说是编程的灵魂了,良好的代码缩进阅读起来十分方便,但是如果没有缩进,代码压根不能看!
程序块要采用统一的缩进和对齐风格编写。整个项目中或者是4
个空格,或者是一个TAB
。不允许混用这两种。
如果是使用TAB
,需保证TAB
键的宽度是4
个空格()。
如果是在原有代码上修改(如Linux内核代码),需和原有代码的缩进、对齐方式保持一致。
建议在编辑器设置中将TAB键宽度设置为4(大多数编辑器都有这个设置),建议统一使用空格进行对齐。
- 缩进要求:
- if else case for while语句需要缩进;
- case语句与所属的switch语句对齐;
- 所有{}需要缩进,extern “”C”“, namespace 块除外,case语句除外。
空格使用:
- 关键字 if else switch case for while 之后要加空格;
- 如果
,;
后面没有立即换行, 即后面有变量或语句时, 要在后面加空格,类似for循环for(int a = 1; a < 100; a++)
每个条件后都加了空格; - 小括号内侧不能有空格, 函数调用(或宏)的名字与括号之间不能有空格;
- 一元操作符
& * + - ~ ! ++ --
要紧贴对应的变量,不能有空格; - 二元操作符
= + - * / % & | ^ == != >= <= > < ? :
两侧要加空格; - 结构体成员操作符
. ->
前后不加空格。
规范示例:
缩进的范例代码:
struct string_t {
int len;
char data[0];
};
//注意大括号的写法,但个人习惯这样写。其实大多数IDE都为你设置好了编码风格,但是如果用linux等就要自己注意了。
struct string_t
{
int len;
char data[0];
};
//-----------------------
#ifdef __cplusplus
extern "C" {
#endif
struct string_t *create_string(int max_len);
struct string_t *copy_string(const char *str);
void release_string(struct string_t *str);
#ifdef __cplusplus
}
#endif
struct string_t *copy_string(const char *str)
{
int len = strlen(str) + sizeof(struct string_t) + 1;
struct string_t *pstr = (struct string_t *)malloc(len);
if (!pstr)
return NULL;
pstr->len = len - sizeof(struct string_t) - 1;
strcpy(pstr->data, str);
return pstr;
}
switch (state) {
case STATE_CONNECT:
...
break;
case STATE_LOGIN:
...
break;
case STATE_NORMAL:
...
break;
default:
break;
}
for (i = 0; i < cnt; ++i) {
if (arr[i] > value) {
list_add(list, arr[i]);
}
}
1.2 语句行
一行只写一条语句,不允许把多个短语句写在一行中。大多数以分号;
算作一条语句。
1.3 大括号
大括号是编码中的灵魂,没有大括号就没有作用域。具体规范如下:}
必须独占一行,有两种例外:- 如果是在if(…){}else if(…){}else{}中,可与else放同一行;
- 如果是在do{}while(…)中,可与while放同一行;
{
可以独占一行,且与上一语句的起始位置对齐。 也可以跟在相应的if、for、do、while、switch、class声明、函数声明后面;
{
,}
的相对位置在整个模块中必须保持一致。 if、for、do、while、switch这几种语句块的{
必须保持相对位置一致。 其他语句块的{
只要求在同类型之间保持相对位置一致即可。
范例
以下这段代码符合checklist要求:
int find_split(const char* str)
{
assert(str);
int len = strlen(str);
for (int i = 0 ; i < len ; ++i) {
if (str[i] == ',' || str[i] == '.') {
return i;
}
}
return -1;
}
以下这几段代码不符合checklist要求:
- if语句和for语句的
{
相对位置不一致(要统一,不要两种风格并存)
int find_split(const char* str)
{
assert(str);
int len = strlen(str);
for (int i = 0 ; i < len ; ++i) {
if (str[i] == ',' || str[i] == '.')
{
return i;
}
}
return -1;
}
{
没有和上一语句的起始位置对齐
int find_split(const char* str)
{
assert(str);
int len = strlen(str);
for (int i = 0 ; i < len ; ++i)
{
if (str[i] == ',' || str[i] == '.')
{
return i;
}
}
return -1;
}
1.4 代码行长度
每行代码不应超过80列。 如果某些行需要超出80列(比如调用win32 API时,由于API参数过多,往往会超出80列),应该折成多行显示。 例外条款:- 注释可以例外;
- 如果字符串单独占一行仍超出80列,可以例外;
2 注释
注释对代码来说十分重要,可以使阅读代码的人能很好的理解代码。好的注释显得尤其重要,因为若干年后,代码作者可能自己都看不懂自己当年所写的代码是什么意思。2.0 总则
注释的目的是提升代码可读性,帮助代码读者更快速的了解代码作者的实际意图。- 注释应重点阐述目的,而非过程;
- 注释应重点阐述隐性知识,即代码无法直接反映的意图、原则,如扩展方法,锁策略,内存分配限制等等;
- 注释应重点阐述模块/函数之间的关联知识。
即对比分析多个函数才能得到的知识,比如:
- 参数及返回值的含义、约束;
- 外部数据的含义、取值范围;
- 函数/模块之间的协作关系;
- 多个变量/函数之间的相互关系;
- ……
- 注释应避免描述显而易见的知识,比如:“这是一个构造函数”,“定义一个整型变量”;
- 注释内容需要和代码实际行为保持一致,不应涉及无关内容,如“今天天气很好”,“checklist规定这里要注释”;
注释需要及时更新,反映代码当前的状态,否则反而误导代码读者; 本条款为阐述、建议性条款;
2.1 声明注释
变量的声明很重要,是对变量含义的进一步解释,而不是只是知道其是什么类型。平时自己写注释最多写下函数的作用,一般很少写的那么详细。但是长久来说这是值得的。
主要注意以下几点:
- 文件头:在头文件(*.h,*.hpp,*.inc等)和源文件头部应注释说明该其功能;
- 函数头:函数头部应注释说明其功能及各参数、返回值的含义(无参构造函数、析构函数、重载的运算符函数可无需注释);
- 全局变量:全局变量应注释说明其功能;
- 常量: 所有常量定义都应注释说明其功能;
- 类型:所有类型定义(包括struct,class,enum,union),都应注释说明其功能;
- 宏定义:所有宏定义应注释说明其功能,如果宏有参数,必须说明参数的用法;