條件述句(conditional)控制其他述句的執行流程,包括 ifelsecase/switch 等。迴圈控制(whilefor)雖然邏輯上也屬於條件,但慣例上獨立討論(見第 16 章)。

15.1 if 述句#

簡單 if-then 述句#

  • 先寫正常路徑,再處理異常情況:讓正常執行路徑一目了然,兼顧可讀性與效能。
  • 注意等號邊界> vs >=< vs <= 的錯誤,如同陣列的 off-by-one 錯誤,務必想清楚等號的情況。
  • 正常情況放 if、異常放 else:讓讀者第一眼就看到正常流程。

把錯誤處理散亂地穿插在 if 與 else 之間,會讓正常路徑難以辨識。應統一將正常情況放在 if 子句,錯誤處理放在 else 子句。

' 不良寫法:正常與錯誤情況混雜
OpenFile( inputFile, status )
If ( status = Status_Error ) Then
    errorType = FileOpenError
Else
    ReadFile( inputFile, fileData, status )
    If ( status = Status_Success ) Then
        ...
' 良好寫法:正常情況一律放前面
OpenFile( inputFile, status )
If ( status = Status_Success ) Then
    ReadFile( inputFile, fileData, status )
    If ( status = Status_Success ) Then
        ...
  • 避免空的 if 子句:若 if 子句為空只靠 else 執行,應反轉條件、去掉 else。
  • 考慮是否需要 else:GM 的研究指出 50%~80% 的 if 述句應該要有 else 子句。即使不需要 else,也應加註解說明原因。
if ( COLOR_MIN <= color && color <= COLOR_MAX ) {
    // do something
}
else {
    // color 無效,可安全忽略此指令
}
  • 測試 else 子句的正確性:不要只測 if 分支,else 也要測試。
  • 檢查 if/else 是否寫反:這是常見的邏輯錯誤。

if-then-else-if 鏈#

當語言不支援 case 述句,或只部分支援時,常會用到 if-then-else-if 鏈。

  • 用布林函式簡化複雜判斷:將複雜的測試條件封裝成命名清楚的布林函式,大幅提升可讀性。
// 難讀
if ( inputCharacter == ' ' || inputCharacter == ',' || ... ) {
    characterType = CharacterType_Punctuation;
}

// 易讀
if ( IsLetter( inputCharacter ) ) {
    characterType = CharacterType_Letter;
}
else if ( IsPunctuation( inputCharacter ) ) {
    characterType = CharacterType_Punctuation;
}
  • 最常見的情況放最前面:減少讀者閱讀例外處理的負擔,也減少執行時的測試次數。
  • 確保涵蓋所有情況:在最後的 else 加入錯誤訊息或 assertion,捕捉未預期的情況。
else {
    DisplayInternalError( "Unexpected type of character detected." );
}
  • 能用 case 就用 case:若語言支援字串或列舉的 case(如 Visual Basic、Ada),比 if-then-else 鏈更清晰易讀。

15.2 case 述句#

不同語言對 case/switch 的支援差異很大:C++/Java 僅支援序數型別逐值比對,Visual Basic 支援範圍與組合,許多腳本語言完全不支援。

排列順序的選擇#

  • 字母或數值順序:各 case 重要性相同時,依 A-B-C 排列便於查找。
  • 正常情況優先:一個正常 case 搭配多個例外時,正常放最前面並加註解。
  • 依頻率排序:最常執行的 case 放前面,方便閱讀也提升效率。

使用技巧#

  • 每個 case 的動作保持簡短:若邏輯複雜,提取成獨立函式再呼叫。
  • 不要為了用 case 而製造假變數:例如只取使用者指令的第一個字元來做 case,會漏掉 “copy”、“cement”、“cellulite” 都匹配到 'c' 的問題。此時應改用 if-then-else 鏈比對完整字串。

若資料不適合簡單分類,就不該硬塞進 case 述句,改用 if-then-else 鏈才是正確做法。

  • default 子句只用於合理的預設值:不要把最後一個合法 case 偷懶寫成 default,這會失去自動文件化的效果,也失去用 default 偵測錯誤的能力。
  • 用 default 偵測錯誤:在 default 中放診斷訊息,對除錯和正式環境都有幫助。
switch ( commandShortcutLetter ) {
    case 'a':
        PrintAnnualReport();
        break;
    case 'q':
        PrintQuarterlyReport();
        break;
    default:
        DisplayInternalError( "Internal Error 905: Call customer support." );
}
  • C++/Java 中避免 fall-through:每個 case 結尾都要加 break。Fall-through 會讓控制結構交疊,修改時極易出錯。
  • 若刻意使用 fall-through,務必清楚標註:加上 // FALLTHROUGH 註解並說明原因。
switch ( errorDocumentationLevel ) {
    case DocumentationLevel_Full:
        DisplayErrorDetails( errorNumber );
        // FALLTHROUGH -- Full 也需要印 Summary

    case DocumentationLevel_Summary:
        DisplayErrorSummary( errorNumber );
        // FALLTHROUGH -- Summary 也需要印 Number

    case DocumentationLevel_NumberOnly:
        DisplayErrorNumber( errorNumber );
        break;

    default:
        DisplayInternalError( "Internal Error 905: Call customer support." );
}

要點#

  • 簡單的 if-else 述句要注意子句順序,確保正常路徑清晰明確。
  • if-then-else 鏈與 case 述句的排列順序應以最大化可讀性為目標。
  • 利用 case 的 default 子句或 if-then-else 鏈的最後一個 else 來捕捉錯誤。
  • 各種控制結構並非生而平等,應針對每段程式碼選擇最適合的結構。