by 一朝成名
这是翻译版本,英文原版是linux源码Documentation文件夹下的CodingStyle
一个良好风格的程序看起来直观、美观,便于阅读,还能有助于对程序的理解,特别在代码量比较大情况下更显现编码素质的重要性。相反没有良好的风格的代码读起来难看、晦涩,甚至有时候一个括号没对齐就能造成对程序的曲解或者不理解。我曾经就遇见过这样的情况,花费了很多不必要的时间在程序的上下文对照上,还debug了半天没理解的程序。后来直接用indent -kr -i8给他转换格式来看了。特此转过来一个关于代码风格的帖子分享一下~
Linux内核编码风格
这是一份简短的,描述linux内核首选编码风格的文档。编码风格是很个人化的东西,而且我也不愿意把我的观点强加给任何人,不过这里所讲述的是我必须要维护的代码所遵守的风格,并且我也希望绝大多数其他代码也能遵守这个风格。所以请至少考虑一下本文所述的观点。
首先,我建议你打印一份GNU的编码规范,然后不要读它。烧掉它,这是一个很高调的具有象征意义的姿态。
Anyway, here goes:
第一章:缩进
制表符是8个字符,所以缩进也是8个字符。有些异端运动试图将缩进变为4(乃至2)个字符深,这跟尝试着将圆周率PI的值定义为3没什么两样。
理由:缩进的全部意义就在于清楚的定义一个控制块起止于何处。尤其是当你盯着你的屏幕连续看了20小时之后,你将会发现大一点的缩进将会使你更容易分辨缩进。
现在,有些人会抱怨8个字符的缩进会使代码向右边移动的太远,在80个字符的终端屏幕上就很难读这样的代码。这个问题的答案是,如果你需要3级以上的缩进,不管缩进深度如何你的代码已经有问题了,应该修正你的程序。
简而言之,8个字符的缩进可以让代码更容易阅读,还有一个好处是当你的函数嵌套太深的时候可以向你提出告警。请留意这个警告。
在switch语句中消除多级缩进的首选的方式是让“switch”和从属于它的“case”标签对
齐于同一列,而不要“两次缩进”“case”标签。比如:
switch (suffix) {
case 'G':
case 'g':
mem <<= 30; break; case 'M': case 'm': mem <<= 20; break; case 'K': case 'k': mem <<= 10; /* fall through */ default: break; } 不要把多个语句放在一行里,除非你有什么东西要隐藏: if (condition) do_this; do_something_everytime; 也不要在一行里放多个赋值语句。内核编码风格超级简单。就是请避免使用怪异的表达式。除了注释、文档和Kconfig之外,不要使用空格来缩进,前面的例子是例外,是有意为之。 选用一个好的编辑器,不要在行尾留空格。 第二章:把长的行和字符串打散 编码风格的意义就在于使用平常使用的工具来维持代码的可读性和可维护性。 每一行的长度的限制是80列,我们强烈建议您遵守这个惯例。 长于80列的语句要打散成有意义的片段。每个片段要明显短于原来的语句,而且放置的位置也明显的靠右。同样的规则也适用于有很长参数列表的函数头。长字符串也要打散成较短的字符串。唯一的例外是超过80列可以大幅度提高可读性并且不会隐藏信息的情况。 void fun(int a, int b, int c) { if (condition) printk(KERN_WARNING "Warning this is a long printk with " "3 parameters a: %u b: %u " "c: %u \n", a, b, c); else next_statement; } 第三章:大括号和空格的放置 C语言风格中另外一个常见问题是大括号的放置。和缩进大小不同,选择或弃用某种放置策略并没有多少技术上的原因,不过首选的方式,就像Kernighan和Ritchie展示给我们的,是把起始大括号放在行尾,而把结束大括号放在行首,所以: if (x is true) { we do y } 这适用于所有的非函数语句块(if、switch、for、while、do)。比如: switch (action) { case KOBJ_ADD: return "add"; case KOBJ_REMOVE: return "remove"; case KOBJ_CHANGE: return "change"; default: return NULL; } 不过,有一种特殊情况,命名函数:它们的起始大括号放置于下一行的开头,这样: int function(int x) { body of function } 全世界的异端可能会抱怨这个不一致性,呃…确实是不一致的,不过所有思维健全的人都知道(a)K&R是 正确的, 并且(b)K&R是正确的。另外,不管怎样函数都是特殊的(在C语言中,函数是不能嵌套的)。 注意结束大括号独自占据一行,除非它后面跟着同一个语句的剩余部分,比如说do语句中的“while”或者if语句中的“else”,像这样: do { body of do-loop } while (condition); 和 if (x == y) { .. } else if (x > y) {
...
} else {
....
}
理由:K&R。也请注意这种大括号的放置方式还能使空(或者差不多空的)行的数量最小化,同时不失可读性。因此,由于你的屏幕上的新行的供应不是可回收的资源(想想25行的终端屏幕),你将会有更多的空行来放置注释。
仅有一个单独的语句时,不用加不必要的大括号。
if (condition)
action();
这点不适用于本身为某个条件语句的一个分支的单独语句。这时应该两个分支里都使用
大括号。
if (condition) {
do_this();
do_that();
} else {
otherwise();
}
3.1:空格
Linux内核的空格使用方格(主要)取决于它是用于函数还是关键字。(大多数)关键字后要加一个空格。值得注意的例外是sizeof、typeof、alignof和__attribute__,这些关键字在一定程度上看起来更像函数(它们在Linux里也常常伴随小括号使用,尽管在C语言里这样的小括号不是必需的,就像“struct fileinfo info”声明过后的“sizeof info”)
所以在这些关键字之后放一个空格:
if, switch, case, for, do, while
但是不在sizeof、typeof、alignof或者__attribute__这些关键字之后放空格。例如,
s = sizeof(struct file);
不要在小括号里的表达式两侧加空格。这是一个反例:
s = sizeof( struct file );
当声明指针类型或者返回指针类型的函数时,“*”的首选使用方式是使之靠近变量名或者函数名,而不是靠近类型名。例子:
char *linux_banner;
unsigned long long memparse(char *ptr, char **retptr);
char *match_strdup(substring_t *s);
在大多数二元和三元操作符两侧使用一个空格,例如下面所有这些操作符:
= + - < > * / % | & ^ <= >= == != ? :
但是一元操作符后不要加空格:
& * + - ~ ! sizeof typeof alignof __attribute__ defined
后缀自增和自减一元操作符前不加空格:
++ --
前缀自增和自减一元操作符后不加空格:
++ --
“.”和“->”结构体成员操作符前后不加空格。
不要在行尾留空白。有些可以自动缩进的编辑器会在新行的行首加入适量的空白,然后你就可以直接在那一行输入代码。不过假如你最后没有在那一行输入代码,有些编辑器就不会移除已经加入的空白,就像你故意留下一个只有空白的行。包含行尾空白的行就这样产生了。
当Git发现补丁包含了行尾空白的时候会警告你,并且可以应你的要求去掉行尾空白;不过如果你是正在打一系列补丁,这样做会导致后面的补丁失败,因为你改变了补丁的上下文。
第四章:命名
C 是一个简朴的语言,你的命名也应该这样。和Modula-2和Pascal程序员不同,C程序员不使用类似ThisVariableIsATemporaryCounter这样华丽的名字。C程序员会称那个变量为
“tmp”,这样写起来会更容易,而且至少不会令其难于理解。
不过,虽然混用大小写的名字是不提倡使用的,但是全局变量还是需要一个具描述性的名字。称一个全局函数为“foo”是一个难以饶恕的错误。
全局变量(只有当你真正需要它们的时候再用它)需要有一个具描述性的名字,就像全局函数。如果你有一个可以计算活动用户数量的函数,你应该叫它“count_active_users()”
或者类似的名字,你不应该叫它“cntuser()”。
在函数名中包含函数类型(所谓的匈牙利命名法)是脑子出了问题——编译器知道那些类型而且能够检查那些类型,这样做只能把程序员弄糊涂了。难怪微软总是制造出有问题的程序。
本地变量名应该简短,而且能够表达相关的含义。如果你有一些随机的整数型的循环计数器,它应该被称为“i”。叫它“loop_counter”并无益处,如果它没有可能被误解的话。类似的“tmp”可以用来称呼任意类型的临时变量。
如果你怕混淆了你的本地变量名,你就遇到另一个问题了,叫做函数增长荷尔蒙失衡综合症。请看第六章(函数)。
|