多年來,就算我在寫其它語言時用的都不是 Allman 風格。但是只要我寫 C,我就會自然而然地用 Allman。而且我甚至有點反感 K&R,更不要說是花括號行為不一致的 Linux K&R 了。
我原本只是想整理一個自己偏好的 Coding style,但是在仔細思考詳細的規則和實際寫程式會遇到的情況後,我發現 K&R 似乎才是唯一的真理。也總算理解 Linux kernel coding style 手冊裡的那段話:
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 are right. Besides, functions are special anyway (you can’t nest them in C)
在此之前,我覺得 K&R 的唯一優點只有「節省行數」(然後把全部的東西擠在一起),但是我發現 K&R 的其它優點,它們甚至讓節省行數都變得微不足道。
所以我想藉由這篇文章,來說明我是如何在寫草稿時還是忠實的 Allman 用戶,突然就變成 K&R 支持者。當然還有一點很重要的是,Coding style 還是有不少主觀因素在內,你完全可以不認同我的任何看法和感覺。這只是一個分享而已。
定義
首先在討論各個 Coding style 前,我想先定義一下我認為的好的 style 是什麼樣子的,有了統一的標準才有比較的依據。以下按權重排序:
- 使用現有規定的風格。這個不用多說,如果專案已經有規定,那就是遵守。不過這篇討論的是你有完全的控制權的情況。
- 看得順眼。就如同工具順手最重要。
- 使用方便。當然沒人想被風格影響實際功能和 Coding 效率。
- 規則簡單且明確。規則越簡單、例外越少就越容易遵守。明確的規則就可以不用再花時間思考。
實例比較
A. typedef
- A:只有一個規則 —— 左右花括號都不換行。
- B:雖然左右花括號的換行行為不一致,但看起還是不錯,實際上這種風格也很常見。可是如果右花括號不換行的話,那
if-else
的右花括號也不換行嗎?還是它們的右花括號行為也要不一致? - C:我是沒看過這種風格。如果左右花括號都換行的話,那
do-while
也是嗎?
B. 初始化 struct
- A:只有一個規則 —— 左右花括號都不換行。
- B:通常習慣上等號(賦值運算子)的右值如果換行的話會縮排。
- C:雖然 B,但是兩個花括號都縮排看起來不太自然。
- D:第1行的左花括號不一定在4的整數倍,這樣導致要嘛成員的縮排也不是4的整數倍,要嘛成員和花括號直接的差不是4,要嘛左花括號和等號之間的不是1個空白。此外太佔水平空間。
這邊的
struct
只是舉例,實務上如果成員這麼少的話從一開始就可以只寫成一行。這裡想表達的是成員很多、或成員名很長、或結構較複雜的struct
。這裡的 4 指的是我用 4 的空白做縮排為例,實際上到底是多少都不影響。
C. 統一初始化陣列
- A:只有一個規則 —— 左右花括號都不換行。
- B:這種簡單的初始化,左右花括號如果強硬規定要換行明顯太不自然了,應該沒有人會這樣寫。
這裡同樣也適用於初始化簡單到可以寫成一行的
struct
的情況。
D. if-else
這個例子可能不是非常好,至少我在寫類似的程式時,會加一些空白行來分隔,不會讓它們就這樣黏著。但是在不加空白行的情況下,A 應該稍微更容易區分各個 if-else
的開頭和結尾。注意,A 的規則還是左右花括號都換行,但是這是指同一個語句(Statement),不同的 if-else
不會疊在一起。
E. 宣告函式原型
你現在已經先打好了一個函數,考慮現在要幫 foobar()
宣告函數原型…
- A:整行複製後,要先刪除行末的左花括號,才能打分號。
- B:整行複製後,可以直接在行末打分號。
再考慮一下如果你一次寫了 5 個函數,現在要一次幫它們宣告函數原型。你已經把它們都整行複製到一起了,它們現在長這樣:
如果用 Vim 的話來說可能是…
- A:
V
、3j
、:s/{/;
、Enter
(使用 Visual line 搜尋{
並替換成;
) - B:
Ctrl-v
、3j
、$
、A
、;
、ESC
(使用 Visual block 到行末 Append;
)
不知道你覺得哪個操作比較快,我自己是更加熟悉 B 的那套操作,而且實際上要按的鍵數也比較少。看來這次是 B 的勝利。不過對於 Vim 的使用者來說,這個差距不是很大。
Coding Style
讓我們來看一下主流的 Coding style 長什麼樣。首先要注意的是,一般說的 K&R 有兩種:
- 第一種是網路上大家普遍討論時認為的 K&R,即所有左右花括號都不換行。以下姑且稱其為 llvm-K&R。
- 第二種是 Linux kernel 所使用的風格,函數的左右花括號換行,其餘的左右花括號都不換行。以下稱其為 linux-K&R。這種才是 Kernighan & Ritchie 的書《The C Programming Language》中真正使用的風格。
嘗試歸納並描述它們的規則:
- llvm-K&R:所有的左右花括號都不換行。
- linux-K&R:所有的左右花括號都不換行,除了函數。
- Allman:流程控制、迴圈、函數的左右花括號都換行(
do-while
的右花括號可能是例外)。typedef
的左花括號換行、右花括號不換行。初始化時左右花括號不換行。
很明顯,llvm-K&R 的規則最簡單且毫無例外。linux-K&R 的規則比 llvm-K&R 多了一個唯一的例外。Allman 的規則就複雜多了。
總結
在實例比較時,我認為風格 A——所有的左右花括號都不換行——在多數情況下都是最好看、自然且直覺的,除了「宣告函式原型」的地方,因為它的操作要更多一點。而且風格 A 的規則也是最少的。
- 如果完全套用風格 A 的話,你得到的是 llvm-K&R。
- 如果你想要在「宣告函式原型」的地方佔有操作優勢的話,你得到的是 linux-K&R。雖然這樣會多一個例外,但也只是多一個而已。多增加的這個例外可以幫你換到操作優勢。
恩… K&R 一家親。至少我認為 K&R 風格是較具優勢的,無論是 llvm- 還是 linux-。
但是還有一點,在我的定義下,「看得順眼」的權重更高。你完全有理由只憑這點就推翻上面的所有比較,然後繼續用你最熟悉的 Coding style。當然你也有可能和我一樣,瞬間跳槽。
另外也可以看看我自己的完整規範:C 語言 Coding Style 規範
留言可能不會立即顯示。若過了幾天仍未出現,請 Email 聯繫:)