2006年1月10日星期二

Linux内核编码风格

Linux 内核编码风格

Linus Torvalds
(我的翻译版)

这是一份描述首选的 Linux 内核编码风格的短文档。编码风格是非常个性化的,我并不强求每个人都赞同我的观点,但是这是我能够维护的所有代码遵守的规则,我也更倾向于其他的代码也可以遵守这个规则。所以请至少考虑一下下面的一些要点。

首先,我建议你打印一份GNU 编码风格,但是不要阅读它。把它烧了,这是一个伟大的象征性的表示。

好了,下面开始:

第一章:缩进

一个 Tab 的长度是8个字符,也就是会产生8个字符的缩进。有些歪理邪说者要求我们缩进4个字符深(甚至是2个字符!),这就如同要求我们把PI的值定义为3一样。

原理:使用缩进的目的是为了清楚的确定代码块从何处开始,在何处结束。特别是在盯着屏幕看了整整20小时之后,你就会很容易的发现使用大缩进的好处。

有些人宣称使用8个字符的缩进会引起代码向右偏移太大,使之在80字符宽的终端屏幕上阅读变得困难。对于这个问题的答案是,假如你使用了3层以上的缩进,一定使你犯了某些原则错误,并需要修改你的程序了。

简而言之,8字符的缩进使你的代码更加易于阅读,另外还有一个优点是可以在你的函数嵌套太深的时候警告你。请注意这种警告。

第二章:为括号定位

C代码风格中的另一个经常遇到的问题是大括号的位置。不像缩进尺寸,选择一种括号的位置而不是选择另一种,包含了一些技术上的原因,但是最好的方法是如同先驱 Kernighan 和 Ritchie 展示的那样,把开括号放在一行的最后,而把闭括号放在一行的开始,就像这样:

if (x is true) {
we do y
}

然而也有一个特殊的情况,也就是函数:他们的开括号是在下一行的行首,就像这样:

int function(int x)
{
body of function
}

全世界的歪理邪说者认为这种不一致是不合理的,没关系──我想大家都知道 (a) K&R 是正确的 (b) K&R 是右撇子。另外,函数本来就是很特殊的东西(在C中,你不能嵌套函数)。(Heretic people all over the world have claimed that this inconsistency is ... well ...inconsistent, but all right-thinking people know that (a) K&R are right and (b) K&R areright.)

注意,闭括号所在的行是空的,除了在闭括号后面跟随的是同一语句的继续的情况下。例如,在 do 语句后面的 "while" ,和 if 语句中的 "else" ,就像下面:

do {
body of do-loop
} while (condition);

和:

if (x == y) {
..
} else if (x > y) {
...
} else {
....
}

原理:K&R

另外,注意到这种括号的定位,同时也使得空白行(或者几乎是空白的行)的数量最小。因此,当你的屏幕的新行的供应是不可更新的资源的时候(在此,请想想25行的终端屏幕),你就可以有更多的空行来放注释。

第三章:命名

C是一种简洁的语言,所以你的命名也必须是这样。不像 Modula-2 和 Pascal 的程序员,C程序员不会使用这样可爱的名字:ThisVariableIsATemporaryCounter。一个C程序员会把它叫做 tmp ,这样容易写,也不是很难理解。然而,尽管大小写混用的名字让人皱眉头,但是描述性的名字对于全局变量来说却是必须的。把一个全局变量叫做 foo 的人,应该枪毙。

全局变量(只有在你真的需要它们的时候才使用)应该有一个具有描述性的名字,全局函数也是。如果你有一个函数是用来为活跃用户计数的,你应该把它叫做 count_active_users() 或类似的名字,你不能叫它为 cntusr() 。

把函数类型编码进函数名(也就是所谓的匈牙利命名法)是脑子有毛病──编译器总是知道函数的类型,也可以检查这些类型,这只会使程序员感到困惑。难怪微软的程序总是 bug 多多呢。

局部变量的名字应该短小而到位。如果你有一些随意使用的整数计数器,只要不会发生误解,你就应该把它们叫做 i 。如果叫它为 loop_counter 是低效的。类似的,tmp 可以用来作为任何类型变量的临时值的存储容器。

如果你担心混淆了局部变量的名字,那你就是有另一个问题,这个问题被称为“函数增加时的激素紊乱综合征”。见下一章。

第四章:函数

函数应该短小精悍,而且应该只做一件事。它们最长只能是填满一到两屏幕的文本(大家都知道,ISO/ANSI 的屏幕大小是80x24),只完成一件事,并把它做好。

函数的长度应该和该函数的复杂程度和缩进层数成反比。如果你需要一个概念上很简单的函数,而这个函数只是一个长(而简单的)分支语句,在其中你要在许多不同的情况下完成许多小事,那么使用一个长函数是可以的。

然而如果你有一个复杂的函数,你甚至怀疑一个不那么有天分的一年级的高中生,甚至都不能理解函数到底是关于什么的,那你就应该更加密切的注意函数长度的上限了。使用一个具有描述性名字的帮助函数(你可以让你的编译器把它们插入(in-line)主体部分,如果你认为这对于性能来说很关键的话,编译器很可能会做的比你好很多。)

函数的另一个量度是局部变量的数量。它们不应该超过5-10个,否则你就做错了。重新考虑一下函数的实现,把它分为几个较小的部分。人脑一般可以轻松的记录大约7个不同的事物,如果有更多,就开始混淆了。也许你觉得自己很聪明,但是也许你应该去理解一下2星期前你写的代码。

第五章: 注释:

注释是好东西,但是也应该避免过度注释的危险。永远不要在注释中解释你的代码是如何工作的:写一段工作机理清楚的代码会更好,解释一段写的很糟糕的代码是浪费时间。

基本上,你只要让你的注释说明你的代码了什么事就行了。另外,尽量避免在一个函数体内部添加注释:如果函数复杂到你必须分部分对它进行注释的时候,你应该重新温故一下第四章的内容。你可以用一些小注释来记录或者警告那些特别技巧性或者特别傻的地方,但是尽量避免过头。相反,你应该尽量把注释放在函数的上方,告诉别人它做了什么,也许也要说明为什么要做这件事。

第六章:你已经弄得一团糟了

没关系,我们都会这样。你或许从你的经典的 UNIX 用户帮助那里听说 "GNU Emas" 会为你自动的格式化你的C源码,你也注意到了这是真的,它的确做到了,但是它使用的默认值,远不是我们所希望的那样(事实上它们比我们随意打字还要糟糕──在 GNU Emacs 里面打字的猴子再多也不能得到一个好的程序)。

因此,你可以不使用 GNU Emacs ,或者使用一个更加合适的配置。要做到后面的那个,你应该把下面的一段代码添加到你的 .emacs 文件里面。


(defun linux-c-mode ()
"C mode with adjusted defaults for use with the Linuxkernel."
(interactive)
(c-mode)
(setq c-indent-level 8)
(setq c-brace-imaginary-offset 0)
(setq c-brace-offset -8)
(setq c-argdecl-indent 8)
(setq c-label-offset -8)
(setq c-continued-statement-offset 8)
(setq indent-tabs-mode nil)
(setq tab-width 8))

这讲定义一个 M-x linux-c-mode 命令。当 hacking 这个模块的时候,如果你把字符串 -*- linux-c -*- 放在开始两行的某个地方的时候,这个模式会被自动的加载。另外,你可能希望添加:

(setq auto-mode-alist (cons '("/usr/src/linux.*/.*\\.[ch]$" . linux-c-mode) auto-mode-alist))

到你的 .emacs 文件里面,如果你希望在你编辑在 /usr/src/linux 目录下的源代码文件的时候自动转换为 linux-c-mode 模式。

但是尽管你没有成功的让 Emacs 正确的排版,你也并没有损失一切:使用“缩进”。

GNU的缩进同样有 GNU Emacs 坏脑子的设置错误。这就是你为什么需要为Emacs给出一些命令行选项。不过,这并不是很糟,因为甚至是GNU缩进的创造者也注意到了K&R的权威(GNU人物并不坏,他们只是在这个问题上被误导了而已),所以你只要给出 "-kr -i8"(代表“K&R,8字符的缩进”)的缩进选项就行了。

缩进有很多选项,特别是遇到注释重新格式化(comment re-formating)的时候,你也许应当看看手册页。但是记住,“缩进”并不能补救糟糕的编程。


尽管好多人都翻过这篇文章了,所以我还是决定翻译一下。下午用了一会儿把它翻完了。Linus的英文读起来真拗口。有些句子都不知道应该怎么翻译比较合适。

游戏之作,不要见笑。

本翻译之版权,就使用和Linus原文一样的授权许可吧──其实我并不知道Linus的原文的授权许可是什么,哈哈。谁告诉我一下啊。
(我想 Linus 的原文也应该是创作共用或者是 GNU Free Documentation License ,沿用原文之授权协议应该不是大错。)

没有评论:

发表评论