條件述句(conditional)控制其他述句的執行流程,包括 if、else、case/switch 等。迴圈控制(while、for)雖然邏輯上也屬於條件,但慣例上獨立討論(見第 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 來捕捉錯誤。
- 各種控制結構並非生而平等,應針對每段程式碼選擇最適合的結構。