编码风格
第九章 编码风格
代码主要是为了写给人看的,而不是给机器看的,只是顺便能用机器执行而已,如果是为了写给机器看那直接写机器码就好了,没必要用高级语言了。代码和语言文字一样是为了表达思想、记载信息,所以一定要写的清楚采纳呢有效地表达。
1.缩进和空白
基本上所有的C代码风格对于空白符的规定规定都差不多,主要有以下几条:
1) 关键字if,while,for与其后的控制表达式的(括号之间插入一个空格分隔,但括号内的表达式应紧贴括号)。例如:
2) 双目运算符的两侧插入一个空格分隔,单目运算符和操作数之间不加空格,例如i = i + 1、++i、!(i < 1)、-x、&[1]等。
3) 后缀运算符和操作数之间也不加空格,例如取结构体成员s.a、函数调用foo(arg1)、取数组成员a[i]。
4) ,号和;号之后要加空格,这是英文的书写习惯,
例如:for (i = 1; i < 10; i++)、foo(arg1, arg2)。
5) 以上关于双目运算符和后缀运算符的规则不是严格要求,有时候为了突出优先级也可以写得再紧凑一些,例如for (i=1; i<10; i++)、distance = sqrt(x*x + y*y)等。但是省略的空格一定不要误导了读代码的人,例如a||b && c很容易让人理解成错误的优先级。
6) 由于标准的Linux终端是24行80列的,接近或大于80个字符的较长语句要折行写,折行后用空格和上面的表达式或参数对齐,例如:
再比如:
7) 较长的字符串可以断成多个字符串然后分行书写,例如:
C编译器会自动把相邻的多个字符串接在一起,以上两个字符串相当于一个字符串“This is such a long sentence that it cannot be held within a line\n”。
8) 有的人喜欢在变量定义语句中用Tab字符,使变量名对齐,这样看起来也很好,但不是严格要求的。
内核关于缩进的规则有以下几条。
1) 要用缩进体现出语句块的层级关系,使用Tab字符缩进,不能用空格代替Tab。在标准的Linux终端上,一个Tab看起来是8个空格的宽度,有些编辑器可以设置一个Tab看起来是几个空格的宽度,建议设成8,这样大的缩进使代码看起来非常清晰。规定不能用空格代替Tab主要是不希望空格和Tab混在一起做缩进,如果混在一起用了,在某些编辑器里把Tab的宽度改了就会看起来非常混乱。
2) If/else、while、do/while、for、switch这些可以带语句块的语句,语句块的{和}应该和关键字写在一起,用空格隔开,而不是单独占一行。例如应该这样写:
很多人习惯这样写:
两种写法用的都很广泛,只要在同一个项目中能保持统一就可以了。
3) 函数定义的{和}单独占一行,这一点和语句块的规定不同,例如:
4) Switch和语句块里的case、default对齐写,也就是说语句块里的case、default相对于switch不往里缩进。例如:
自己命名的标号(用于goto)必须顶头写不缩进,而不管标号下的语句缩进到第几层。
5) 代码中每个逻辑段落之间应该用一个空行分隔开。例如每个函数定义之间应该插入一个空行,头文件、全局变量定义和函数定义之间也应该插入空行,例如:
6) 一个函数的语句列表如果很长,也可以根据相关性分成若干组,用空行分隔,这条规定不是严格要求,一般变量定义语句组成一组,后面要加空行,return之前要加空行,例如:
2.注释
单行注释采用/* comment */的形式,用空格把界定符和文字分开。多行注释最常见的是这种形式:
也有一些 更花哨的形式:
或者:
使用注释的场合主要有以下几种:
- 整个源文件的顶部注释。说明此模块的相关信息,例如文件名、作者和版本历史等,顶头写不缩进。例如内核源代码kernel/sched.c的开头:
- 函数注释。说明此函数的功能、参数、返回值、错误码等,写在函数定义上侧,和此函数定义间不留空行,顶头写不缩进。
- 相对独立的语句组注释。对这一组语句做特别说明,卸载语句组上侧,和此语句组之间不留空行,与当前语句组的缩进一致。注意,说明语句组的注释一定要写在语句组上面,不能写在语句组下面。
- 代码行右侧的简短注释。对当前代码行做特别说明,一般为单行注释,和代码之间至少用一个空格隔开,一个源文件中所有的右侧注释最好能上下对齐。函数内的注释要尽可能少用。注释只是用来说明你的代码能做什么(比如函数接口定义),而不是说明怎样做的,只要代码写的足够清晰,怎样做是一目了然的,如果你需要用注释才能解释清楚,那就表示你的代码可读性很差,除非是特别需要提醒注意的地方才使用函数内注释。
- 复杂的结构体定义比函数更需要注释。
- 复杂的宏定义和变量定义也需要注释。
3.标识符命名
标识符命名应遵循以下原则:
- 标识符的命名要清晰明了,可以使用完整的单词和大家易于理解的缩写。短的单词可以通过去元音形成缩写,较长的单词可以取单词的头几个字母形成缩写,也可以采用大家基本认同的缩写。例如count写成cnt,block写成blk,length写成len,window写成win,message写成msg,temporary可以写成temp,也可以进一步写成tmp。
- 内核风格规定变量、函数和类型采用全小写加下划线的方式命名,常量(宏定义和枚举常量)采用全大写加下划线的方式命名。上面举例的函数名radix_tree_insert、类型名struct radix_tree_root、常量名RADIX_TREE_MAP_SHIFT等。有一种变量命名风格叫匈牙利命名法(Hungarian notation),用变量名的前缀记录变量的类型,例如iCnt、pMsg、IpszBlk等。
- 全局变量和全局函数的命名一定要详细,不惜多用几个单词多写几个下划线,例如函数名radix_tree_insert,因为它们在整个项目的许多源文件中都会用到,必须让使用者明确这个变量或函数是干什么用的。局部变量和只在一个源文件中调用的内部函数的命名可以简略一些,但不能太短,不要使用单个字母做变量名,只有一个例外:用i、j、k做循环变量是可以的。
- 针对中国程序员的一条特别规定:禁止用汉语拼音作为标识符名称,可读性极差。
4.函数
每个函数都应该设计得尽可能简单,简单得函数才容易维护。应遵循以下原则:
- 实现一个函数只是为了做好一件事情,不要把函数设计成用途广泛、面面俱到的,这样的函数肯定会而且往往不可重用,维护困难。
- 函数内部的缩进层次不宜过多,一般以少于4层为宜。如果缩进层次太多就说明设计得太复杂了,应该考虑分隔成更小得函数来调用(这称为Helper Function)。
- 函数不要写的太长,建议在24行得标准终端上不超过两屏,太长会造成阅读困难,如果一个函数超过两屏就应该考虑分隔函数了。如果一个函数在概念上是简单的,只是长度很长,这倒没关系。例如函数由一个大的switch组成,其中有非常多的case,这是可以的,因为各个case之间互不影响,整个函数的复杂度只能等于其中一个case的复杂度,这种情况很常见,例如TCP协议的状态实现。
- 执行函数就是执行一个动作,函数名通常应包含动词,例如get_current、radix_tree_insert。
- 比较重要的函数定义上面必须加注释,说此函数的功能、参数、返回值、错误码等。
- 另一种度量函数复杂度的办法是看有多少个局部变量,5到10个局部变量就已经很多了,局部变量再多就很难维护了,应该考虑分隔函数。
5.indent工具
Indent工具可以把代码格式化成某种风格,例如将代码格式化成内核风格:
-kr选项表示K&R风格,-i8表示缩进8个空格的长度。如果没有指定-nut选项,则每8个缩进空格会自动用一个Tab代替。注意indent命令会直接修改原文件,而不是打印到屏幕上或者输出到另一个文件,这一点和很多UNIX命令不同。可以看出,-kr -i8两个选项格式化出来的代码已经很符合本章介绍的代码风格了,添加了必要的缩进和空白,较长的代码行也会自动给折行。美中不足的是没有添加适当的空行,因为indent工具也不知道哪几行代码在逻辑上是一组的,空行还是需要自己手动添,当然,原有的空行肯定不会被indent删去的。