本章聚焦於「小規模的程式設計」——建立個別類別及其子程式的具體步驟。 其中介紹的虛擬碼程式設計流程(Pseudocode Programming Process, PPP),能減少設計與文件撰寫的工作量,同時提升兩者的品質。

9.1 建立類別和子程式的步驟概述#

建立類別的步驟#

類別的建構通常是一個迭代過程,包含以下關鍵步驟:

  1. 建立類別的整體設計:定義類別的職責、隱藏的「秘密」、對外介面所呈現的抽象概念,以及主要的公開方法與重要的資料成員。
  2. 建構類別中的每個子程式:建構個別子程式的過程中,往往會發現需要額外的子程式,這些新發現可能會回頭影響整體設計。
  3. 審查與測試整個類別:個別子程式完成後,應對整個類別進行審查與測試,找出單一子程式層級無法發現的問題。
flowchart TD
    step1["1. 建立類別的整體設計"]
    step2["2. 建構每個子程式"]
    step3["3. 審查與測試整個類別"]
    step1 --> step2
    step2 --> step3
    step2 -- "發現需調整設計" --> step1

建立子程式的步驟#

較複雜的子程式適合採用系統化的方法來建構,主要活動依序為:

  1. 設計子程式(Design the routine)
  2. 檢查設計(Check the design)
  3. 撰寫程式碼(Code the routine)
  4. 檢查程式碼(Check the code)

這些步驟之間可視需要反覆進行。若品質不佳,應毫不猶豫地退回先前步驟重新來過。

9.2 如何寫好虛擬碼#

虛擬碼(Pseudocode) 是一種非正式、類似英語的表示法,用來描述演算法、子程式或程式的運作方式。PPP 定義了一套運用虛擬碼來簡化程式碼建立過程的方法。

有效虛擬碼的撰寫準則#

  • 使用精確描述特定操作的類英語敘述
  • 避免目標程式語言的語法元素——虛擬碼的價值在於能在比程式碼更高的層次進行設計
  • 意圖層次(level of intent)撰寫——描述做法的意義,而非如何在目標語言中實作
  • 撰寫得足夠細緻,使得從虛擬碼產生程式碼幾乎是自動化的過程;若太高階則可能忽略關鍵細節

糟糕的虛擬碼會包含目標語言的指標語法(如 *hRsrcPtr)或特定函式(如 malloc()),並且聚焦於「如何撰寫程式碼」而非「設計的意義」。這種虛擬碼無法轉換為有用的註解。

PPP 的好處#

好處說明
簡化審查審查幾行虛擬碼比審查幾十行程式碼容易得多
支援逐步精煉(Iterative Refinement)從高階設計逐步細化到虛擬碼,再到原始碼,在每個層級捕捉該層級的錯誤
簡化變更在虛擬碼階段修改比寫完程式碼後修改成本低得多
最小化註解工作虛擬碼直接轉化為程式碼中的註解,省去事後補寫註解的麻煩
容易維護註解與程式碼共存,不像其他設計文件容易與實作脫節

9.3 透過 PPP 建立子程式#

flowchart TD
    design["設計子程式"]
    pseudo["撰寫虛擬碼"]
    comment["虛擬碼轉為註解"]
    code["填入程式碼"]
    check["檢查程式碼"]
    finish["收尾"]
    design --> pseudo
    pseudo --> comment
    comment --> code
    code --> check
    check --> finish
    check -- "品質不佳,退回重做" --> design

設計子程式#

設計是建構子程式的第一步,包含以下活動:

活動說明
檢查前置條件確認子程式的工作已被明確定義,且符合整體設計
定義子程式要解決的問題釐清子程式隱藏的資訊、輸入、輸出、前置條件(Preconditions)與後置條件(Postconditions)
命名子程式好的名稱應清晰且毫無歧義;若難以命名,往往代表設計不夠明確
決定如何測試在撰寫子程式時就規劃測試案例
研究標準函式庫檢查是否已有可重用的既有程式碼
思考錯誤處理考慮所有可能出錯的情境,並有意識地選擇處理策略
思考效率大多數系統中,優先確保介面良好抽象與程式碼可讀性;少數效能關鍵的系統,則依架構所訂的資源與速度預算來設計
研究演算法與資料型別查閱演算法書籍,避免從零重新發明

撰寫虛擬碼#

  1. 先寫一段簡潔的標頭註解,描述子程式的用途——若難以摘要,代表需要重新理解子程式的角色
  2. 填入高階虛擬碼,描述子程式的邏輯流程
  3. 思考資料:若資料操作是子程式的重要部分,先定義關鍵資料型別
  4. 檢查虛擬碼:退一步思考如何向他人解釋,請人檢視幾行虛擬碼
  5. 反覆嘗試多種方案,在虛擬碼階段選出最佳設計再開始編碼

一旦開始編碼,就會對程式碼產生情感投入,更難捨棄不良設計。因此務必在虛擬碼階段充分迭代。

撰寫程式碼#

將虛擬碼轉化為實際程式碼的步驟:

  1. 撰寫子程式宣告:寫出介面定義,將標頭註解轉為程式語言的註解
  2. 將虛擬碼轉為高階註解:寫出子程式的首尾語句(如 {}),將虛擬碼轉為行內註解
  3. 在每行註解下方填入程式碼:每段虛擬碼註解對應一個程式碼段落,通常展開為 2 到 10 行程式碼
  4. 檢查是否需要進一步分解:若某行虛擬碼展開出過多程式碼,可將其提取為新子程式,或遞迴地對該段再次套用 PPP

檢查程式碼#

  • 心智檢查:在腦中逐一執行每條路徑,包括正常路徑、端點和所有例外狀況(可自行「桌面檢查」或與同儕進行走查/審查)
  • 編譯子程式:將編譯器的警告層級設到最嚴格,消除所有錯誤訊息與警告的根本原因
  • 在除錯器中逐步執行:確認每一行程式碼都如預期般運作
  • 測試程式碼:使用先前規劃的測試案例進行測試,必要時建立腳手架(Scaffolding)
  • 移除錯誤:若子程式在此時仍有大量錯誤,考慮重寫而非修補——修補通常代表理解不完整

專業程式設計師與業餘愛好者最大的差異之一,在於從「迷信」走向「理解」。如果你經常懷疑是編譯器或硬體的錯誤,你仍停留在迷信的領域——事實上約 95% 的錯誤源自程式設計師自身。若子程式能運作但你不知道為什麼,請務必研究到徹底理解為止。

收尾工作#

收尾檢查清單
  • 檢查子程式的介面:所有輸入輸出資料是否已處理,所有參數是否被使用
  • 檢查整體設計品質:子程式是否只做一件事、與其他子程式是否為鬆耦合、是否具防禦性
  • 檢查變數:命名是否準確、有無未使用的物件、初始化是否正確
  • 檢查語句與邏輯:是否有差一錯誤(Off-by-one)、無窮迴圈、不當巢狀、資源洩漏
  • 檢查排版:空白是否恰當地呈現邏輯結構
  • 檢查文件:虛擬碼轉成的註解是否仍然準確
  • 移除多餘的註解:若虛擬碼註解與緊接其後的良好命名函式呼叫重複,則可移除

9.4 PPP 的替代方案#

替代方案說明
測試先行開發(Test-First Development)在撰寫任何程式碼之前先寫測試案例,驅動設計與實作
重構(Refactoring)透過一系列保持語意不變的轉換來逐步改善程式碼,利用「程式碼異味」(Code Smells)來識別需要改善的區段
契約式設計(Design by Contract)將每個子程式視為具有前置條件與後置條件的契約
亂搞式開發(Hacking)若你發現自己經常寫到一半走進死胡同、在編碼途中迷失思路、或盯著螢幕不知從何下手——這些都是 PPP 能幫助你的明確信號

要點#

  • 類別與子程式的建構是迭代過程,建構個別子程式時獲得的洞見往往會回頭影響類別的設計
  • 好的虛擬碼應使用可理解的英語、避免特定程式語言的特性、在意圖層次撰寫
  • PPP 是細節設計的有力工具,虛擬碼直接轉化為註解,確保註解的準確性與實用性
  • 不要滿足於第一個想到的設計,在虛擬碼中反覆嘗試多種方案,選出最佳方案再開始編碼
  • 在每個步驟都檢查你的工作,並鼓勵他人一同檢查,以最低的成本在最早的階段捕捉錯誤