縮進風格

在程序設計中，縮進風格（indent style）是管理代碼塊縮進以表達程序結構的一種約定。本條目主要討論自由形式語言，例如C及其後裔，但這也可以（並經常）適用於大多數其他程式語言（尤其是花括號程式語言），其中的空白字符則並不重要。縮進風格是代碼風格的一個方面。

縮進在大多數程式語言中不是必要條件，而只是作為輔助符號。不過，縮進有助於更好地向人類閱讀者表達程序的結構。尤其是用於澄清控制流程結構（例如條件或循環）與其內部、外部代碼之間的關係。不過，部分語言（例如Python和occam）使用縮進而非花括號或關鍵詞來確定結構，這被稱為越位規則。在這種語言中，縮進對編譯器或解釋器有意義，而不僅僅是清晰度或風格問題。

## 研究

儘管縮進風格被廣泛使用，但關於其價值的研究卻很少。Weissman在1974年進行的初步實驗沒有顯示出任何效果。

2023年，Morzeck等人的實驗表明，對於嵌套的`if`語句，縮進有顯著的積極影響：閱讀非縮進代碼所需的時間平均比縮進代碼多179%。Hanenberg等人的後續實驗證實了這一巨大影響（儘管在該實驗中，非縮進代碼的閱讀時間僅增加了113%），並揭示了閱讀時間的差異可以由（縮進代碼中）可跳過的代碼來解釋。在另一個關於JSON對象的實驗中，閱讀非縮進代碼的時間甚至增加了544%。

## 花括號位置

縮進風格的主要區別在於複合語句的花括號（`{...}`）的位置，這通常是為涵蓋一個控制聲明（`if`、`while`、`for`...）。下表展示了本條目中討論的所有風格的所在位置。為了一致性，縮進深度（字符數）統一使用4個空格表示，這未考慮各風格中首選的縮進深度。

花括號位置｜風格

K&R及變種： 1TBS、Stroustrup、Linux內核、BSD KNF

```c
while (x == y) {
   something();
   somethingelse();
}
```

Allman

```c
while (x == y)
{
   something();
   somethingelse();
}
```

GNU

```c
while (x == y)
 {
   something();
   somethingelse();
 }
```

Whitesmiths

```c
while (x == y)
   {
   something();
   somethingelse();
   }
```

Horstmann

```c
while (x == y)
{   something();
   somethingelse();
}
```

Pico

```c
while (x == y)
{   something();
   somethingelse(); }
```

Ratliff

```c
while (x == y) {
   something();
   somethingelse();
   }
```

Lisp

```c
while (x == y) {
   something();
   somethingelse(); }
```

APL

```c
#define W(c,b) {while(c){b}}
W(x==y,f();b();)
```

## C/C++ 風格

C、C++和其他花括號程式語言的編碼風格屬性包括但不限於：

- 花括號相對於其他代碼元素的位置
- 使用制表符還是空格
- 是否將單語句塊包裹在花括號中。支持者認為這更安全，因為插入新語句不會導致控制流與縮進不一致。反對者則認為這會增加代碼長度，因為除了 `else if` 結構和 `do{}while` 塊之外，塊的閉合花括號需要占用一行。

### K&R

Kernighan & Ritchie (K&R) 風格常在C、C++代碼中使用，並且是許多衍生風格的基礎。它用於原始的Unix內核、Kernighan和Ritchie的《C程序設計語言》一書，以及Kernighan和Plauger的《編程風格的元素》中。

儘管《C程序設計語言》沒有明確定義這種風格，但它始終如一地遵循著它。書中提到：

花括號的位置不那麼重要，儘管人們對此持有執著的信念。我們選擇了這種廣受歡迎的風格。選定一種適合你的風格，然後堅持使用它。

在這種風格中，函數的左花括號和右花括號各自獨占一行，並與聲明保持相同的縮進；而函數體內的語句則多縮進一級。然而，函數內部的多語句塊的左花括號與其控制子句在同一行，而右花括號則獨占一行，除非它後面緊跟 `else` 或 `while` 等關鍵字。

示例代碼：

```c
int main(int argc, char *argv[])
{
   while (x == y) {
       do_something();
       do_something_else();
       if (some_error)
           fix_issue(); // 无花括号的单语句块
       else
           continue_as_usual();
   }
   final_thing();
}
```

#### 埃及花括號

多行代碼塊的非對齊花括號被暱稱為「埃及花括號」（或「埃及括號」），因為它們類似於古埃及人畫像中手臂一上一下的奇特姿勢。

#### 單語句

單語句塊不使用花括號，這容易導致諸如goto fail漏洞這樣易被忽視的錯誤。

#### 變種：1TBS (OTBS)

「唯一的真括號風格」（The One True Brace Style）（縮寫為1TBS或OTBS）是K&R風格的一個變種，其中單語句塊的括號不被省略。

```c
bool is_negative(int x)
{
   if (x < 0) {
       return true;
   } else {
       return false;
   }
}
```

儘管C/C++等語言不要求這樣做，但為單語句塊使用花括號可以確保在插入新語句時，控制流不會與縮進不一致，從而避免諸如蘋果公司臭名昭著的goto fail漏洞等問題。

一些來源對「唯一的真括號風格」的定義存在分歧——它可能根據特定語言中最流行的風格而略有不同，有些作者可能會根據主觀偏好將截然不同的風格宣稱為OTBS，而其他人則認為這是K&R風格的「黑客行話」稱呼。

#### 變種：Linux內核

Linux內核源碼樹使用K&R風格的一種變種。林納斯·托瓦茲建議貢獻者遵循該風格。其屬性包括：

- 使用制表符縮進（而非空格），並設定製表位為8個字符。
- 花括號布局與K&R一致：函數定義的花括號獨占一行，複合語句的左花括號與控制子句在同一行，並用空格隔開。
- `switch` 語句中的標籤（label）與外圍塊對齊（只有一級縮進）。
- 最大行長為100個字符，儘管首選2020年之前的80個字符限制。
- 複合語句（如if、while和do-while）的單語句體不需要用花括號包圍。但是，如果 `if-else` 語句中的任何一個子語句需要花括號，則兩個子語句都應包裹在花括號中：

```c
int power(int x, int y)
{
       int result;

       if (y < 0) {
               result = 0;
       } else {
               result = 1;
               while (y-- > 0)
                       result *= x;
       }
       return result;
}
```

#### 變種：Java

大量的Java代碼使用K&R風格的一種變種，其中左花括號不僅對於函數內的塊在同一行，對於類或方法聲明也是如此。這種風格之所以普遍，主要是因為Sun Microsystems最初的風格指南使用了這種K&R變種。因此，Java API的大部分標準原始碼都是用這種風格編寫的。這在ActionScript和JavaScript中也是一種流行的縮進風格，與Allman風格並列。

#### 變種：Stroustrup

比雅尼·史特勞斯特魯普（Bjarne Stroustrup）在他的書中（如《程序設計：原理與實踐（使用C++）》和《C++程序設計語言》）針對C++調整了K&R風格。

與上述變種不同，Stroustrup不使用「cuddled else」（即 `} else {` 寫在同一行）。因此，Stroustrup會這樣寫：

```c
   if (x < 0) {
       puts("Negative");
       negative(x);
   }
   else {
       puts("Non-negative");
       nonnegative(x);
   }
```

Stroustrup對類擴展了K&R風格，寫法如下：

```cpp
   class Vector {
   public:
       // construct a Vector
       Vector(int s) :elem(new double[s]), sz(s) { }
       // element access: subscripting
       double& operator[](int i) { return elem[i]; }
       int size() { return sz; }
   private:
       // pointer to the elements
       double * elem;
       // number of elements
       int sz;
   };
```

Stroustrup不對標籤 public: 和 private: 進行縮進。此外，在這種風格中，函數的左花括號另起一行，而類的左花括號則與類名在同一行。

Stroustrup允許將短函數寫在一行中。在Emacs編輯器中，Stroustrup風格是一種可用的命名縮進風格。Stroustrup在他的現代《C++核心指南》中鼓勵使用基於K&R的C++布局。

#### 變種：BSD KNF

伯克利軟體套件（BSD）作業系統使用一種有時被稱為內核標準格式（Kernel Normal Form, KNF）的風格。雖然主要用於內核代碼，但也廣泛用於使用者空間代碼。它本質上是貝爾實驗室第6版和第7版Unix原始碼中使用的K&R風格的一個詳盡文檔化的變體。

SunOS內核和使用者空間使用類似的縮進風格。與KNF一樣，這也基於AT&T的風格文檔，有時被稱為Bill Joy標準格式（Bill Joy Normal Form）。SunOS指南發布於1996年。Bill Shannon編寫的 cstyle 程序可以驗證原始碼文件的縮進正確性。

在這種風格中，硬制表符（vi中的ts）保持為8列，而軟制表符通常定義為輔助工具（vi中的sw），並設置為4。硬制表符用於縮進代碼塊，而額外的軟制表符（4個空格）用於所有必須分成多行的連續行。

此外，函數調用在括號前不使用空格，但C語言的原生語句如 `if`, `while`, `do`, `switch` 和 `return` 會使用空格（如果 `return` 使用括號的話）。在其頂層塊中未聲明局部變量的函數，也應在其左花括號後留一空行。

### Allman風格

Allman風格以埃里克·奧爾曼（Eric Allman）命名。有時也被稱為「BSD風格」，因為Allman為BSD Unix編寫了許多實用程序（但這不應與上述的「BSD KNF風格」混淆）。

這種風格將與控制語句關聯的花括號放在下一行，縮進到與控制語句相同的級別。花括號內的語句則縮進到下一級。

```c
while (x == y)
{
   something();
   something_else();
}

final_thing();
```

這種風格類似於Pascal和Transact-SQL中使用的標準縮進，其中的花括號等同於關鍵字 `begin` 和 `end`。

這種風格的後果是，縮進的代碼通過幾乎全是空白的行與包含它的語句清晰地分隔開來，且右花括號與左花括號在同一列對齊。有些人認為這使得查找匹配的括號變得容易。這種塊風格還將代碼塊與關聯的控制語句劃清界限。注釋掉或刪除控制語句或代碼塊，或進行代碼重構時，不太可能因懸空或缺失的花括號而引入語法錯誤。此外，它與外部函數塊的花括號位置一致。

#### 變種：Allman-8

Allman-8使用Linux內核變種K&R的8空格縮進和80列限制。據稱這種風格有助於提高在投影儀上的可讀性。此外，縮進大小和列限制有助於創建一個視覺提示，以識別代碼塊的過度嵌套。這些優勢結合起來，有助於為新的開發者和學習者提供隱含的指導，以管理代碼複雜性。

### Whitesmiths風格

Whitesmiths風格，有時也被稱為Wishart風格，最初用於第一個商業C編譯器——Whitesmiths編譯器的文檔中。它在Windows早期也很流行，因為它用於三本有影響力的Windows編程書籍：《Programmer's Guide to Windows》、《Programming Windows》和《Windows 3.0 Power Programming Techniques》。

根據Jargon File，Whitesmiths風格與Allman風格在1991年曾是最常見的括號風格，當時兩者的流行程度大致相當。

這種風格將與控制語句關聯的花括號放在下一行，並進行縮進。花括號內的語句縮進到與花括號相同的級別。

與Ratliff風格一樣，右花括號與花括號內的語句縮進相同。

```c
while (x == y)
   {
   something();
   something_else();
   }

final_thing();
```

這種風格的優點與Allman風格相似。代碼塊與控制語句清晰地分開。花括號與代碼塊的對齊強調了整個代碼塊在概念上和程序上都是一個複合語句。縮進花括號強調了它們從屬於控制語句。結束的花括號不再與語句對齊，而是與開始的花括號對齊。

`else if` 被視為一種語句，就像預處理器語句 `#elif` 一樣。

### GNU風格

像Allman和Whitesmiths風格一樣，GNU風格將花括號單獨放在一行，縮進兩個空格，而在打開函數定義時除外，此時它們不縮進。在這兩種情況下，包含的代碼都相對於花括號再縮進兩個空格。

這種風格由理察·斯托曼普及，其布局可能受到他編寫Lisp代碼背景的影響。在Lisp中，相當於塊（progn）的是一等數據實體，給予它自己的縮進級別有助於強調這一點，而在C中，塊僅僅是語法。這種風格也可以在20世紀60年代和70年代的一些ALGOL和XPL程式語言教科書中找到。

儘管不完全屬於縮進，GNU編碼風格還包括在函數名之後——參數列表的左括號之前——加一個空格。

```c
static char *
concat (char *s1, char *s2)
{
 while (x == y)
   {
     something ();
     something_else ();
   }
 final_thing ();
}
```

這種風格結合了Allman和Whitesmiths的優點，從而消除了Whitesmiths可能存在的花括號不突出的缺點。一個缺點是結束的花括號不再與它概念上所屬的語句對齊。另一個可能的缺點是它可能通過使用兩個視覺縮進級別來表示一個概念級別而浪費空間，但在現實中這不太可能，因為在單級縮進的系統中，每級通常至少是4個空格，與GNU風格中的2 * 2個空格相同。

GNU編碼標準推薦這種風格，幾乎所有GNU計劃軟體的維護者都使用它。

Steve McConnell在他的書《代碼大全》中建議不要使用這種風格：他在一個使用該風格的代碼示例上標記了一個「Coding Horror」圖標，象徵著特別危險的代碼，並指出它阻礙了可讀性。Linux內核編碼風格文檔也建議不要使用這種風格，敦促讀者燒掉GNU編碼標準的副本作為「偉大的象徵性姿態」。

### Horstmann風格

Cay S. Horstmann在1997年版的《Computing Concepts with C++ Essentials》中通過將塊的第一個語句放在與左花括號同一行來調整Allman風格。這種風格也用於Jensen和Wirth的《Pascal User Manual and Report》中的示例。

```c
while (x == y)
{   something();
   something_else();
   //...
   if (x < 0)
   {   printf("Negative");
       negative(x);
   }
   else
   {   printf("Non-negative");
       nonnegative(x);
   }
}
final_thing();
```

這種風格結合了Allman的優點，即保持花括號的垂直對齊以利於閱讀，並易於識別塊，同時也節省了K&R風格的一行。然而，2003年的版本現在完全使用Allman風格。

### Pico風格

這是Pico語言設計者最常用的風格。Pico缺乏return語句，並使用分號作為語句分隔符而不是終止符。它產生了這種語法：

```
stuff(n):
{ x: 3 * n;
 y: do_stuff(x);
 y + x }
```

其優點和缺點類似於使用K&R風格節省螢幕空間。一個額外的優點是，起始和結束花括號在應用上是一致的（都與一行代碼共享空間），而K&R風格中，一個花括號與一行代碼共享空間，另一個花括號則獨占一行。

### Ratliff風格

在《Programmers at Work》一書中，流行的dBase-II和-III第四代程式語言背後的原始程式設計師C. Wayne Ratliff討論了一種類似於1TBS的風格，但結束的花括號與嵌套塊的縮進對齊。他表示這種風格最初記錄在Digital Research公司的材料中。這種風格有時被稱為「橫幅（banner）」風格，可能因其類似於掛在杆子上的橫幅。在這種風格中（它對Whitesmiths的關係就像K&R對Allman的關係），結束控制與列表中的最後一項縮進相同（因此適當地失去了顯著性）。這種風格可能使某些人的視覺掃描更容易，因為任何塊的標題是該級別唯一突出的東西。

```c
// In C
for (i = 0; i < 10; i++) {
    if (i % 2 == 0) {
        do_something(i);
        }
    else {
        do_something_else(i);
        }
    }
```

## 衍生C語言風格

以下風格可以被認為是「衍生」的C風格，因為它們很大程度上受到其他非C語言縮進風格的影響。它們可能適用於大部分用這些其他語言編寫的項目中的C代碼部分，為了保持項目核心代碼的一致「外觀和感覺」，這凌駕於使用更傳統的C風格的考量之上。

### Lisp風格

雖然GNU風格有時被描述為Lisp程式設計師縮進的C代碼，但人們甚至可以更進一步，將結束花括號一起插入塊的最後一行。這種風格使得縮進成為區分代碼塊的唯一方式，但其優點是不包含無資訊的行。這很容易被稱為Lisp風格，因為這種風格在Lisp代碼中非常常見。在Lisp中，表達式樹末尾的相同括號的分組旨在表明，視覺上跟蹤嵌套級別不是使用者的工作，使用者只需理解樹的結構。

這種風格的傳統Lisp變體偏好非常窄的縮進級別（通常是兩個空格），因為Lisp代碼通常嵌套很深。Lisp僅具有表達式，沒有獨特的語句類；函數參數大多縮進到同一水平，以說明它們在封閉表達式中的共享狀態。這也是因為，除了括號之外，Lisp傳統上是一種非常簡潔的語言，甚至省略了作為無資訊樣的常見樣板代碼，例如 `if : then | else` 塊中的 `else` 關鍵字，而是將其統一渲染為 `(if expr1 expr2 expr3)`。

```c
// C
for (i = 0; i < 10; i++)
   {if (i % 2 == 0)
       {do_something(i);}
    else
       {do_something_else(i);
        do_third_thing(i);}}
```

```lisp
;; Lisp
(dotimes (i 10)
 (if (= (rem i 2) 0)
     (do-something i)
   (progn
     (do-something-else i)
     (do-third-thing i))))
```

### Haskell風格

Haskell是一種花括號可選的語言，也就是說，下面的兩組代碼在語義上是相等的：

```haskell
braceless = do
 text <- getContents
 let
   firstWord = head $ words text
   bigWord = map toUpper firstWord
 putStrLn bigWord
```

```haskell
braceful = do
 { text <- getContents
 ; let
     { firstWord = head $ words text
     ; bigWord = map toUpper firstWord
     }
 ; putStrLn bigWord
 }
```

在Haskell中，布局（layout）可以替代花括號。通常，過程式 `do`的段落和一般程序文本會省略花括號和分號，但這種風格通常用於由一對括號或花括號組成的列表、記錄或其他句法元素，並用逗號或分號分隔。如果跟在關鍵字 `where`、`let` 或 `of` 後的代碼省略了花括號和分號，那麼縮進就是有意義的。

### APL風格

作為APL通常是多麼簡潔的例子，這是康威生命遊戲的step函數實現：

```apl
life←{⊃1⍵∨.∧3 4=+/+⌿¯1 0 1∘.⊖¯1 0 1⌽¨⊂⍵}
```

APL風格的C代碼類似於APL代碼的簡潔風格，常用於其實現中。這種風格由Arthur Whitney開創，並大量用於他自己的項目K的實現中。J程式語言也是用這種風格實現的。值得注意的是，並非所有APL實現都使用這種C風格，例如GNU APL和Dyalog APL。

除了APL風格的C縮進外，通常變量名也會縮短為單字符或雙字符，以減少縮進量和跨越多行的表達式。

## 制表符、空格及縮進尺寸

縮進的尺寸通常與風格無關。通常，程式設計師使用相同寬度的空白字符來縮進每個代碼塊，常用的寬度從1到4個空格不等。1983年對PASCAL代碼進行的一項實驗發現，縮進大小顯著影響可理解性。2到4個字符之間的縮進大小被證明是最佳的。

許多早期程序使用制表符來縮進，從而簡化輸入和節約原始碼文件的大小。Unix編輯器通常將制表符視為等同八個字符，而Macintosh和Windows環境將它視作四個字符，這使代碼在各環境間交換時產生一種混亂。現代的編程編輯器通常可以設置任意的縮進尺寸，並會插入適當的制表符與空格。對Ruby、許多shell腳本語言和某些形式的HTML格式，通常為每個縮進級別使用兩個空格。

使用制表符還是空格作為縮進字符是編程界的一項持續爭論。傑米·加文斯基等一些程式設計師認為空格而非制表符有助增加跨平台可移植性。而如WordPress編碼規範的作者則認為制表符增加了可移植性。GitHub上前40萬個存儲庫的調查顯示，空格更為常見。

## 工具

目前已有許多電腦程式可以自動校正縮進風格（依照程序作者或使用者的偏好）以及制表符表示的縮進長度。其中很著名的一個是`indent`，這個程序包含在許多類Unix作業系統中。

在Emacs中，有多種命令可用於自動解決縮進問題（如 `M-x indent-region`）。

Elastic tabstops是一種需要文本編輯器支持的制表風格，當塊中的一行的長度改變時，整個文本塊將自動對齊。

## 其他考慮

### 丟失塊蹤跡

在某些情況下存在著丟失塊邊界的軌跡的風險。這通常在包含許多複雜語句的大量代碼中看到，這些複合語句嵌套了許多層的縮進。當程式設計師滾動到一大堆嵌套語句的底部時，他可能已經忘記了哪些控制語句轉到哪裡。長複合語句可能是代碼異味或過於複雜的標誌，面對這個問題的程式設計師可能會考慮代碼重構以期待它在未來有更好的體驗。

依賴計算左花括號的程式設計師可能會在K&R等風格中遇到困難，因為起始花括號在視覺上沒有與控制語句分開。更多依賴縮進的程式設計師會從K&R等垂直緊湊的風格中受益更多，因為塊更短。

為了避免丟失對 `for` 等控制語句的跟蹤，可以使用大縮進（如8單位寬的硬制表符），並將大函數分解為更小、更易讀的函數（Linux內核採用了這種方式）。

一些文本編輯器允許程式設計師在塊的兩個對應花括號之間跳轉。例如，在vi中按 `%` 鍵。另一種保持塊意識的方法是在右花括號後使用注釋：

```c
for (int i = 0; i < total; i++) {
   foo(bar);
} //for (i)
```

```c
if (x < 0) {
  bar(foo);
} //if (x < 0)
```

### 聲明的插入

在使用標準的Unix行編輯器ed時，K&R風格能防止一個常見的錯誤。在控制語句與循環塊的開啟花括號之間錯誤地插入的語句將使循環體變為單次執行。

```c
for (int i = 0; i < 10; i++)
   whoops(bar);   /* repeated 10 times, with i from 0 to 9 */
{
   only_once();   /* Programmer intended this to be done 10 times */
} //for (i) <-- This comment is no longer valid, and is very misleading!
```

K&R風格通過將控制語句和開啟括號保持在同一行來避免此問題。

## 外部連結

- C Style: Standards and Guidelines: Defining Programming Standards for Professional C Programmers , Prentice Hall, ISBN 0-13-116898-3 / ISBN 978-0-13-116898-5 (full text is also online). Straker, David (1992).
- Contextual Indent
- GNU Coding Standards
- Computers/Programming/Development_Tools/Source_Code_Formatters/

### 制表符與空格

- Tabs versus Spaces: An Eternal Holy War by Jamie Zawinski
- Why I prefer no tabs in source code by Adam Spiers
- Why I love having tabs in source code (archived)
- Elastic tabstops – the solution to the tabs-versus-spaces issue