跳轉到內容

計算機程式設計/編碼風格

0% developed
來自華夏公益教科書

計算機程式設計中,有許多編碼約定用於確保程式碼一致,並提高程式碼質量,特別是包括正確性、可讀性、可維護性和速度。各個專案、社群、程式碼庫和指南選擇特定的約定,形成編碼標準風格指南。“程式設計風格”主要指的是低階約定,例如格式或語言結構的選擇(例如gotoreturn),但也可能指的是大型程式碼結構,甚至在軟體工程中的總體設計。這些更高級別的風格主題通常被稱為“哲學”,例如Unix哲學

雖然一些約定被廣泛認為優於其他選擇,但另一些約定有幾個常見的替代方案,每個方案都有其優點和缺點,不同的標準做出不同的選擇。一致性是一個普遍的價值觀,因此即使在特定情況下另一種選擇可能更好,但為了保持一致性而做出例外所付出的代價通常超過了收益。然而,如果在特別令人信服的情況下或在規定的情況下,有時會做出例外,不同的標準對一致性的重視程度不同。

除了指定要遵循的良好實踐外,編碼風格還識別出不良實踐,稱為“反模式”或“程式碼異味”,並可能推薦特定的解決方案。本指南討論語言中立問題,列出各種約定的優缺點。

程式碼結構

[編輯 | 編輯原始碼]

非程式碼

[編輯 | 編輯原始碼]

左手比較

[編輯 | 編輯原始碼]

在使用一個符號(通常是單個等號 (=),例如 Visual Basic)進行賦值,而使用另一個符號(通常是兩個等號 (==) 進行比較(例如 C/C++JavaActionScript 3PHPPerl 數值上下文以及過去 15 年的大多數語言)的語言中,並且賦值可以在控制結構中進行,採用左手比較風格有優勢:在任何比較中將常量或表示式放在左邊。 [1] [2]

以下兩行程式碼分別展示了左手和右手比較風格,應用於一行 Perl 程式碼。在這兩種情況下,程式碼都將變數 $a 中的值與 42 進行比較,如果匹配,則執行後續程式碼塊中的程式碼。

if ($a == 42) { ... }  # A left-hand comparison checking if $a equals 42.
if (42 == $a) { ... }  # Recast, using the right-hand comparison style.

當開發者不小心輸入了 = 而不是 == 時,就會出現差異。

if ($a = 42) { ... }  # Inadvertent assignment which is often hard to debug
if (42 = $a) { ... }  # Compile time error indicates source of problem

第一行(左手)程式碼現在包含一個潛在的微妙缺陷:它不再執行之前的操作,而是將 $a 的值設定為 42,然後始終執行後續程式碼塊中的程式碼。由於這在語法上是合法的,程式設計師可能不會注意到錯誤,軟體可能會帶著錯誤釋出。

第二行(右手)程式碼包含語義錯誤,因為無法將數值分配給數值。這會導致在編譯程式碼時生成診斷訊息,因此程式設計師無法忽視此錯誤。

某些語言內建了對意外賦值的保護。例如,Java 和 C# 不支援為了這個原因自動轉換為布林值。

還可以透過使用靜態程式碼分析工具來減輕這種風險,這些工具可以檢測到這個問題。

迴圈和控制結構

[編輯 | 編輯原始碼]

使用邏輯控制結構進行迴圈也有助於良好的程式設計風格。它有助於閱讀程式碼的人更好地理解程式的執行順序(在指令式程式設計語言中)。例如,在虛擬碼中

i = 0
 
while i < 5
  print i * 2
  i = i + 1
end while
 
print "Ended loop"

上面的程式碼片段遵循命名和縮排風格指南,但下面使用“for”結構的程式碼可能更容易閱讀

for i = 0, i < 5, i=i+1
  print i * 2
 
print "Ended loop"

在許多語言中,經常使用的“對於範圍內的每個元素”模式可以縮短為

for i = 0 to 5
  print i * 2
 
print "Ended loop"

在允許使用花括號的程式語言中,風格文件通常要求即使在可選的情況下,也必須在所有控制流結構中使用花括號。

for (i = 0 to 5) {
  print i * 2;
}
 
print "Ended loop";

這可以防止程式流錯誤,這些錯誤可能很耗時才能跟蹤,例如在結構的末尾引入終止分號(常見的輸入錯誤)

 for (i = 0; i < 5; ++i);
    printf("%d\n", i*2);    /* The incorrect indentation hides the fact 
                               that this line is not part of the loop body. */
 
 printf("Ended loop");

...或者在第一行之前新增另一行

 for (i = 0; i < 5; ++i)
    fprintf(logfile, "loop reached %d\n", i);
    printf("%d\n", i*2);    /* The incorrect indentation hides the fact 
                               that this line is not part of the loop body. */
 
 printf("Ended loop");

另一種更傳統的風格是明確地指示巢狀。這意味著開括號和閉括號位於同一列

for( index = 0 ; index < size ; ++index )
{
    arrayA[ index ] = arrayB[ index ] ;
}

這清楚地指示了程式碼塊的開始和結束,因此很容易從程式碼中識別出來。

指示關係

[編輯 | 編輯原始碼]

關係用多種方式表示。最簡單的方法是將關鍵字和函式名稱與其引數相關聯。這透過將關鍵字或函式名稱直接放在引數旁邊來指示。

if( a == b )
{
    ...
}

接下來是引數本身。在條件語句中存在多個引數的情況下,將這些引數水平和垂直排列成對齊的形式表示它們之間的關係。

if( ( a == b ) ||
    ( a == c )    )
{
    ...
}

關係一直延伸到頂層 - 函式之間的關係、元件之間的關係以及模組之間的關係。它們可以是不同型別的,但都需要明確指示。

指示關係非常重要,因為它是連線點的部分 - 展示程式碼如何聯絡在一起的一部分。在構建模組時,非常重要的一點是,在設計模組時要確保其結構在關係方面易於理解,從一個明顯的起點開始,並從該點跟蹤程式碼。

分散程式碼並對齊

[編輯 | 編輯原始碼]

這對於可讀性非常重要。基本原則是

  1. 用空格分隔每個元件部分。
  2. 以有意義的方式對齊所有內容。

這樣一來,人們就可以輕鬆地掃描程式碼,檢視程式碼的模式。這不僅對於理解程式碼非常重要,而且對於查詢異常,以及作為整理和整合程式碼的工具也至關重要。

包含大量“噪聲”的程式碼(大量不必要的變化和凌亂)會導致人們浪費大量時間在它上面。編寫良好且格式良好的程式碼易於快速使用。它可以讓使用者輕鬆地“透過現象看本質”。

壓縮的程式碼

for(i=0;i<s;i++){a[i]=b[i];}

分離的程式碼

for( index = 0 ; index < size ; index++ )
{
    arrayA[ index ] = arrayB[ index ] ;
}

清晰的格式和有意義的名稱使程式碼更易讀,更容易理解。

意義和一致性

[編輯 | 編輯原始碼]

意義是指以傳達意義的方式進行編碼。如果一個 for 迴圈只是使用字母“i”作為索引,這意義就不大。然而,如果使用“index”這個詞,那就更有意義了。

一致性是指在出現相同型別的情況時使用相同的名稱。例如,如果對“index”使用不同的詞語,例如“i”、“index”、“inx”、“indx”,這就不一致了——這是在編碼中引入不必要“噪聲”的許多領域之一。然而,如果始終使用“index”,那麼產生的程式碼將更加易於閱讀和理解。

儘量減少未知數的數量,並最大限度地增加已知數的數量非常重要。

硬編碼和軟編碼

[編輯 | 編輯原始碼]

硬編碼(通常稱為“魔數”)會產生難以維護的程式碼。程式碼會失去對數字代表含義及其出現位置的理解。硬編碼應該用列舉值或 #defines(軟編碼)替換。大多數編譯器可以將列舉值處理為無型別的數字,因此這比使用宏(#defines)更安全的方法。軟編碼的專案可以非常快速、安全地更新。硬編碼的專案更新起來可能非常耗時且非常危險。

當列表中的專案放在單獨的行上(垂直列表)時,有時將專案分隔符新增到最後一個專案之後,以及每個專案之間被認為是一種良好的做法——最常見的是逗號,因此它也被稱為使用**尾隨逗號**。例如在 C 中

const char *array[] = {
    "item1",
    "item2",
    "item3",  // still has the comma after it
};

這確保了每行都是一個單獨的專案,無論順序如何,或者是否還有另一個專案緊隨其後。這樣就不需要在以前是最後一行列表的行的後面新增逗號,也不需要從新最後一個專案中刪除逗號,當列表專案重新排序或在末尾新增或刪除專案時。除了減少乏味和防止語法錯誤之外,它還有兩個更微妙的好處。首先,在使用版本控制時,兩個檔案版本之間的行差異將只顯示專案的插入和刪除(以及重新排序),而不會為新增或刪除尾隨逗號額外新增一行。[3] 其次,在某些語言(例如 Python)中,相鄰的字串文字會連線起來,因此列表中間缺少逗號,不會導致語法錯誤,而是會導致兩個相鄰的專案連線起來,這可能是一個難以發現的錯誤。

這在某些語言中的某些結構中得到支援,例如CJavaPython中的陣列(列表)。在其他情況下,尾隨逗號是語法錯誤,或者在末尾用空條目擴充套件列表。即使是支援尾隨逗號的語言,這些語言中並非所有類似列表的語法結構都需要支援它。ECMAScript就是一個語言發生變化的例子:版本 3 不允許尾隨逗號——儘管這只是在Internet Explorer中強制執行的——而版本 5 允許可選的尾隨逗號。偶爾會有一些微妙之處。例如,在一些 FORTRAN 方言中,尾隨逗號被解釋為一個額外的空引數,然後一個空引數列表 FOO() 被認為是一個單獨的空引數;因此不能傳遞空引數列表。許多系統透過允許傳遞一個額外的引數來處理這種方言差異。[4] 在 Python 中,尾隨逗號允許在元組(以及其他型別)中使用,並且一個 1 元組由帶有尾隨逗號的表示式定義,如 1,——括號是允許的,會產生 (1,)(1 元組)而不是簡單的帶括號的表示式 (1),但定義元組的是逗號(這裡尾隨);這裡空元組由空括號 () 定義。[5][6]

在列表中使用尾隨逗號可以與在許多語言中使用分號作為語句終止符進行比較,在這些語言中,語句通常寫在單獨的行上,並且允許或需要尾隨分號。這與ALGOL和早期的Pascal形式形成對比,在這些形式中,分號嚴格地是語句分隔符,並且尾隨分號是非法的;參見Pascal:分號作為語句分隔符

尾隨逗號也可以用在水平列表中,例如 f(x, y,),它具有使列表更容易修改的好處,就像在垂直列表中一樣,但這通常被認為是不雅觀的,並且不太常見。

尾隨逗號通常在解析器中處理,作為短語語法的部分。類似的現象是分號插入,它通常在詞法分析期間完成。但是,在某些情況下,它們會結合起來——ECMAScript 包含分號插入(在詞法分析器中),但也包含一些短語生成規則中的可選尾隨分號。

註釋和文件

[編輯 | 編輯原始碼]

對程式碼進行註釋非常重要。人們無法讀懂他人的想法。註釋有兩種用途

  1. 確保程式碼按預期編寫。這種註釋通常在編寫程式碼之前放在程式碼中。它作為一種手段來確保程式碼執行它應該執行的操作。
  2. 確保程式碼的描述足夠詳細,以便任何檢視程式碼的人都可以輕鬆地理解程式碼的設計和目的。

文件非常重要。它獨立於程式碼,但與程式碼相關聯。它包含諸如規範、設計說明、測試和測試結果等文件。

另請參見

[編輯 | 編輯原始碼]

參考文獻

[編輯 | 編輯原始碼]



請僅在書籍標題頁新增 {{alphabetical}}


參考文獻

[編輯 | 編輯原始碼]
  1. Sklar, David (2003). PHP Cookbook. O'Reilly. {{cite book}}: 未知引數 |coauthors= 被忽略 (|author= 建議使用) (幫助), 食譜 5.1 "避免 == 和 = 混淆", 第 118 頁
  2. "C 程式設計常見問題解答:常見問題". Addison-Wesley, 1995. 2010 年 11 月. {{cite web}}: 檢查日期值: |date= (幫助)
  3. "為什麼在資源中使用尾隨逗號?", Puppet 食譜, Dean Wilson
  4. 9.9.4 醜陋的空引數
  5. 5.3. 元組和序列
  6. TupleSyntax
華夏公益教科書