除了最單純的循序執行之外,所有控制結構都仰賴布林運算式(Boolean Expressions)的求值結果。本章探討布林運算式的撰寫技巧、複合述句與空述句的使用、深層巢狀結構的處理、結構化程式設計的理論基礎,以及控制結構與程式複雜度之間的關係。

19.1 布林運算式#

使用 true / false 而非數值#

在布林測試中應使用 truefalse 識別字,而非 01。使用數值旗標的問題在於:讀者無法從程式碼本身判斷 1 代表的是「真」、「假」,還是某個列舉值。大多數現代語言都有內建的布林型別,應善加利用。

  • 隱式比較:寫 while ( !done ) 而非 while ( done == false ),減少讀者需要記住的項目數量,讀起來也更接近自然語言

簡化複雜的布林運算式#

  • 拆解為中間布林變數:將龐大的測試拆成帶有語意名稱的局部變數,讓每個子運算式各自表達一個概念
  • 抽取為布林函式:即使只用一次,將測試移到具有良好命名的函式中,能在程式碼層面引入抽象概念,比註解更可靠
  • 使用決策表(Decision Tables):當條件涉及多個變數時,以資料結構取代複雜的 if/case 邏輯,減少控制結構的錯誤機會

正面表述布林運算式#

多重否定會嚴重損害可讀性。改善策略包括:

  • 將否定條件轉為肯定條件,並交換 if/else 區塊的程式碼
  • 選擇能反轉真值的變數名稱(例如用 errorDetected 取代 !statusOK
  • 套用**乘法定理(DeMorgan’s Theorems)**來化簡帶有否定的布林運算式

DeMorgan’s Theorems 的核心操作:對每個運算元取反、交換 andor、再對整個運算式取反。例如 !a || !b 等價於 !(a && b)

括號、求值順序與數線排列#

  • 充分使用括號:不要依賴語言的運算子優先順序,括號能同時提升可讀性和正確性
  • 數線排列法(Number-Line Order):將數值比較按照數線從小到大排列。例如 MIN <= i && i <= MAX 表示 i 在範圍內;i < MIN || MAX < i 表示 i 在範圍外
  • 與 0 的比較準則:布林變數用隱式比較;數值用 balance != 0;C 的字元指標用 *charPtr != '\0';指標用 bufferPtr != NULL

短路求值與語言特性#

許多語言(C++、Java 的 &&/||)採用短路求值(Short-Circuit Evaluation):若第一個運算元已能決定結果,就不會評估第二個。這在防止除零錯誤或陣列越界時很有用,但也隱藏了運算順序的依賴。

Java 的 &|(邏輯運算子)會完整評估所有運算元,不具備短路行為。與 &&/||(條件運算子)的行為不同,使用時需特別注意。

常見問題#

  • 在 C 系語言中,可將常數放在比較的左側(如 if ( MIN == i ))來防止把 == 誤寫成 =
  • 在 Java 中,比較物件值應使用 a.equals(b) 而非 a == b(後者比較的是參考)

19.2 複合述句(述句區塊)#

**複合述句(Compound Statement)**是用大括號 {} 包裹起來、作為單一述句處理的一組述句。撰寫準則:

  • 成對撰寫大括號:先寫 for (...) { },再填入內容,就不會遇到大括號不匹配的問題
  • 即使只有一行也加大括號:單行述句在維護時容易擴展為多行,若缺少大括號就容易出錯

19.3 空述句#

空述句(Null Statement)是僅由分號組成的述句。處理準則:

  • 將分號獨立放一行並縮排,或使用空大括號 {} 強調
  • 定義 DoNothing() 巨集或行內函式,明確表達「此處故意不做任何事」
  • 考慮將副作用從迴圈控制移至迴圈本體,改用非空迴圈以提升可讀性

19.4 馴服危險的深層巢狀結構#

研究顯示,多數人無法理解超過三層的巢狀 if。深層巢狀直接違反「管理複雜度」這項軟體首要技術命題。

降低巢狀深度的技巧#

點擊展開:降低巢狀深度的完整技巧列表
  • 重新測試部分條件:將深層巢狀拆成多段程式碼,每段用更完整但較扁平的條件測試,以空間換取可讀性
  • 使用 break 區塊:以 do { ... } while (false) 搭配 break 建立一段可中途跳出的區塊(需團隊共識才適合使用)
  • 轉換為 if-then-else 鏈:將巢狀 if 改寫為扁平的 if/else if/else 結構,消除冗餘測試
  • 轉換為 case 述句:適用於整數型別的多路分支,可讀性極高
  • 將深層巢狀抽取為獨立函式:把迴圈內的分支邏輯移入專屬函式,主迴圈只呈現分派邏輯
  • 使用物件導向多型:建立抽象基底類別與子類別,利用多型方法取代 case/if 分支;進一步可搭配工廠方法(Factory Method)集中建立物件
  • 使用狀態變數(見 17.3 節)
  • **使用守衛子句(Guard Clauses)**提前返回(見 17.1 節)
  • 使用例外處理(見 8.4 節)
  • 徹底重新設計:深層巢狀可能是對問題理解不足的訊號
mindmap
  root((降低巢狀深度))
    重構手法
      抽取為獨立函式
      徹底重新設計
    語言機制
      break 區塊
      case 述句
      例外處理
      物件導向多型
    模式技巧
      守衛子句提前返回
      狀態變數
      if-then-else 鏈
      重新測試部分條件

巢狀深度過深不代表必須修改,但你應該有充分的理由來解釋為何不做修改。

19.5 程式設計基礎:結構化程式設計#

**結構化程式設計(Structured Programming)源自 Dijkstra 於 1969 年的經典論文。其核心思想是程式應僅使用單一進入、單一退出(Single-Entry, Single-Exit)**的控制結構。

結構化程式設計由三種基本構件組成:

  • 循序(Sequence):述句依序執行,如賦值與函式呼叫
  • 選擇(Selection):依條件選擇執行路徑,如 if-then-else 與 case 述句
  • 迭代(Iteration):重複執行一組述句,如 for、while 迴圈
flowchart LR
    subgraph S1["循序"]
        direction LR
        A1["A"] --> A2["B"] --> A3["C"]
    end

    subgraph S2["選擇"]
        direction TB
        B1{"條件"} -- "真" --> B2["路徑 A"]
        B1 -- "假" --> B3["路徑 B"]
        B2 --> B4["合流"]
        B3 --> B4
    end

    subgraph S3["迭代"]
        direction TB
        C1["進入迴圈"] --> C2{"條件成立?"}
        C2 -- "是" --> C3["迴圈體"]
        C3 --> C2
        C2 -- "否" --> C4["離開迴圈"]
    end

Bohm-Jacopini 定理(1966)證明了任何控制流程都能僅用循序、選擇、迭代三種結構來建構。breakcontinuereturnthrow-catch 等結構雖然增加了便利性,但應以批判的眼光審視其使用。

19.6 控制結構與複雜度#

控制結構是程式整體複雜度的主要貢獻者。良好的使用方式能降低複雜度,反之則會大幅提升。

McCabe 的循環複雜度#

Tom McCabe 提出的**循環複雜度(Cyclomatic Complexity)**以「決策點」的數量衡量函式的複雜度:

  1. 從 1 開始計算(代表函式的直通路徑)
  2. 每遇到 ifwhilerepeatforandor 各加 1
  3. case 述句中每個分支加 1

判讀基準

  • 0-5:函式大致沒問題
  • 6-10:應考慮簡化
  • 10 以上:應將部分邏輯抽取到獨立函式中
flowchart TD
    Start["從 1 開始計算"] --> Step1["遇到 if / while / for / and / or → 各 +1"]
    Step1 --> Step2["遇到 case 述句 → 每個分支 +1"]
    Step2 --> Result{"計算結果"}
    Result -- "0 ~ 5" --> R1["沒問題"]
    Result -- "6 ~ 10" --> R2["應考慮簡化"]
    Result -- "10 以上" --> R3["應拆分為多個函式"]

    style R1 fill:#2d6a4f,color:#fff
    style R2 fill:#e9c46a,color:#000
    style R3 fill:#e76f51,color:#fff

決策點上限 10 並非鐵律。含有多個 case 的 switch 述句可能超過此值但仍合理,重點在於降低任何時刻需要同時理解的項目數量。

其他複雜度指標#

除了 McCabe 指標之外,還有許多面向能衡量複雜度:使用的資料量、巢狀層數、程式行數、變數的跨距(Span)與存活時間(Live Time)、輸入輸出量等。有些研究者也開發了結合多種指標的複合度量。

要點#

  • 撰寫簡潔且可讀的布林運算式,對程式碼品質有顯著貢獻
  • 深層巢狀使函式難以理解,但有多種技巧可以相對容易地避免
  • 結構化程式設計的核心概念至今仍然適用:任何程式都能由循序、選擇、迭代三種結構組合而成
  • 最小化複雜度是撰寫高品質程式碼的關鍵