名稱:第一版沒有「PPP」這個名字
我逐字查過《程式碼大全》第一版(1993 簡體中譯)第 4 章「建立子程序的步驟」(p.31–44):
- 全章沒有出現過「偽代碼」這三個字,也沒有 “Pseudocode Programming Process” 或縮寫 PPP。
- 第一版把這個東西叫 PDL(程序設計語言,Program Design Language),把流程叫 「PDL 到程式碼流程」(p.33、p.43、p.44)。
- 「Pseudocode Programming Process (PPP)」是第二版(2004)才確立的名稱,不是本書(第一版)的用語。舊卡標題上的 PPP 已撤下。
本卡只依據第一版第 4 章寫成,每條標書內頁碼;引用照原文,只做簡→繁與明顯 OCR 錯字修正;書中的「子程序」在本卡統一寫成常式。
🎯 什麼情境該想到我
當你要寫一個新常式/函式、卻盯著空白畫面不知從何下手時。
第 4 章的定位(p.31):「本章詳細講述了在建立一個常式時的典型步驟……本章的中心內容是如何編寫小規模的程式,以及編寫對各種規模專案都十分關鍵的程式的特定步驟。」
⚙️ 怎麼用
第 0 步:先知道整體順序(4.1,p.31)
「在建立程式過程中引入的許多低層次細節問題,並不需要按某一特定順序來進行,但是一些主要活動——設計程式、檢查程式、常式編碼、檢查程式碼,則應該按圖 4-1 的順序來進行。」(p.31,圖 4-1 在 p.32)
也就是三大段:4.3 設計常式 → 4.4 常式編碼 → 4.5 檢查常式。
PDL 該寫成什麼樣(4.2,p.31–32)★
PDL 是 Caine、Farber、Gordon 共同開發的(書中 OCR 作「Came、Father 和 Gordon」),1975 年發表後曾作過重大修改(p.31)。書強調「PDL 之間的好壞是有判別的」,並給四條方針(原文照列,p.31–32):
- 用模擬英語的語句來精確描述每一個特定操作。
- 避免使用最終程式語言的語句。
「PDL 使你比在程式碼稍高階的層次上進行設計工作。當使用程式語言進行建立時,就又回到了低層次上,從而得不到由於在高層次上進行設計的好處,而且會受到不必要的程式語言語法規則的限制。」(p.31)
- **在設計意向這一層次上寫 PDL。**說明方法的意義,而不是描述如何用目標語言實現。
- 在足夠低的層次上寫出 PDL,它幾乎可以自動生成程式碼。「如果 PDL 寫得太簡略,可能會在編碼過程中忽略問題細節。應該精確地使用 PDL 以方便編碼。」(p.32)
書給的壞 PDL(p.32,違反了上面幾乎所有原則):
Increment resource number by 1
allocate a dlg struct using malloc
if malloc() returns NULL then return 1
invoke OSrsrc_init to initialize a resource for the operating system
*hRsrcPtr = resource number
return 0
「之所以稱之為一個錯誤使用 PDL 的典型,是因為它使用了像
*hRsrcPtr這種特定的 C 語言指標標誌和malloc()這個特定的語言函數,即它採用了程式碼語句。這段 PDL 的中心是如何寫程式碼,而不是說明設計意義。」(p.32)
書給的好 PDL(p.32–33,同一個操作):
Keep track of current number of resources in use
If another resource is available
Allocate a dialog box structure
If a dialog box structure could be allocated
Note that one more resource is in use
Initialize the resource
Store the resource number at the location provided by the caller
Endif
Endif
Return TRUE if a new resource was created; else return FALSE
「它完全是用自然語言寫成的,沒有使用任何目標程式語言語句。在第一段 PDL 中,它只能用 C 語言來實現,而第二段卻並沒有限制所使用的語言。同時,第二段 PDL 也是在意圖層次上寫成的。」(p.33)
為什麼值得這樣寫——書列的五個好處(p.33):
| 好處 | 書怎麼說 |
|---|---|
| 評審更容易 | 「不必檢查原始碼就可以評審詳細設計」,並減少評審程式碼本身的工作 |
| 幫助實現逐步細化 | 結構設計 → 細化為 PDL → 細化為原始碼;每次細化之前都檢查設計,「在每個層次上都可以發現當前層次的錯誤,從而避免影響下一層次的工作」 |
| 變動容易 | 「幾行 PDL 改起來要比一整頁程式碼容易得多。你是願意在藍圖上改一條線還是在房屋中拆掉一堵牆?」——關鍵是「在投資最少時找出錯誤,以降低改錯成本」 |
| 極大減少註釋工作量 | 一般流程是先寫程式碼再加註釋;PDL 流程中「PDL 本身就是註釋」,而「從程式碼到註釋的花費要比從註釋到程式碼高得多」 |
| 比其他形式的設計文件容易維護 | 其他方式下設計與程式碼是分隔的,改了一個另一個就對不上;PDL 流程中「只要直接維護註釋,那麼關於設計的 PDL 文件就是精確的」 |
書自己的定位(p.33):「PDL 並不是詳細設計的唯一工具,但是 PDL 和 PDL 到程式碼流程的確是有用的工具。」
4.3 設計常式(p.33–36)——書列的完整步驟
書用 RecordErrorMessage()(依錯誤代碼輸出錯誤訊息)當貫穿全章的例子。設計階段的步驟(原文小標,見圖 4-2,p.34):
- 檢查先決條件(p.34)——動手前先確認這個常式的工作任務是否已定義、是否和整個結構設計融為一體、是否真的會被呼叫;「至少,在專案的要求定義中就涉及到它」。→ 見 工具-建構的先決條件
- 定義這個常式將要解決的問題(p.34)——結構設計至少要指出這四項:
- 這個常式將要隱含的資訊
- 這個常式的輸入
- 這個常式的輸出,包括受到影響的全域變數
- 這個常式將如何處理錯誤
- 給常式命名(p.34–35)——「好的常式名字往往是一個高品質軟體的標誌之一」;命名困難通常代表你對它的功能還不清楚。
「一個模稜兩可的名字就像是一個在進行競選辯論的政治家,似乎他在說著什麼,可是當你仔細聽時,又分辨不出他的話到底有什麼意義。」(p.34–35)
「如果產生一個模稜兩可名字的原因是模稜兩可的結構設計,那麼就應注意這個危險信號,這時應追回去改進結構設計。」(p.35) - 決定如何測試常式(p.35)——「在編寫常式時,最好能同時考慮如何測試。這對進行單元測試工作是很有益處的。」
- 考慮效率(p.35)——分兩種情形:效能不是主要的 → 做成高度模組化、可讀性強,日後好替換;效能重要 → 結構設計應規定速度與記憶體額度,照指標設計即可。
「除了以上指明的情況以外,不必浪費精力去考慮個別常式的執行效率。最佳化的收益主要來自高層次設計,而不是個別常式,只有在高層次設計某方面有缺陷時,才需要進行微觀最佳化,而這點只在程式全部完成時才會知道。除非必要,不要浪費時間進行增量改進。」(p.35)
- 研究演算法和資料結構(p.35)——「同時提高編碼品質和效率的最有效辦法是重新使用好的程式碼」;與其自己發明別人已寫過博士論文的東西,不如花幾分鐘瀏覽演算法論著。
- 編寫 PDL(p.35–36)——從抽象到具體:
-
最抽象的部分是最開始的註釋,先寫一個關於這個常式目的的精確說明。「如果在編寫這部分說明時感到困難,那說明需要對這個常式在整個軟體中的地位和作用作出更深刻的理解。」(p.35)
-
然後才寫高層次 PDL。書的成品(p.35–36):
This routine outputs an error message based on an error code supplied by the calling routine. The way it outputs the message depends on the current processing state, which it retrieves on its own. It returns a variable indicating success or failure. set the default status look up the message based on the error code if the error code is valid determine the processing method if doing interactive processing print the error message interactively and declare success else doing batch processing if the batch message file opens properly log the error message to the batch file, close the file, and declare success else the message code is not valid notify the user that an internal error has been detected
「應該注意的是這個 PDL 是在一個相當高的層次上寫成的。它使用的不是程式語言,而是用自然語言來精確表達設計思想的。」(p.36)
-
- 考慮資料(p.36)——「如果資料操作是程式的主要部分,那麼在考慮程式的邏輯結構之前,考慮主要資料是必要的。」
- 檢查 PDL(p.36)——請別人幫忙看一下或聽一下你的說明。
「也許你認為請別人看一個只有 11 行的 PDL 是很愚蠢的,但你會對這樣做的結果感到驚奇。PDL 使假設和高層次錯誤比程式語言程式碼容易被發現。人們往往更願意檢查一個只有幾行的 PDL,而不願去檢查一個有 35 行的 C 或 Pascal 常式。」(p.36)
「要確認對常式做什麼和將怎樣做已經有了清楚透徹的了解。如果在 PDL 這一層次上對這點還沒有概念上的了解,那麼在編碼階段了解它的機會還有多少呢?」(p.36) - 逐步細化(p.36)——
「在開始編碼之前,要盡可能多使用 PDL 嘗試一些想法。一旦開始編碼,就會對所寫的程式碼產生愛惜之情,這時,要再想把它扔掉重新開始是非常困難的。」(p.36)
「通常的思想是逐步細化用 PDL 寫成的常式,直到可以在每行 PDL 語句下面添加一行程式碼而成為常式為止……要不斷地細化 PDL 並對其作出進一步說明,直到你看到這樣做是在浪費時間時,再開始實際的編碼工作。」(p.36)
4.4 常式編碼(p.37–42)——PDL 怎麼變成程式碼
書列的實現步驟(見圖 4-3,p.37):
- 書寫常式說明(p.37)——先寫介面語句(程序/函數宣告),並把原來的抽象說明用目標語言的註釋形式寫在 PDL 之上。「這時是指出介面假設的好時機。」(p.38)
- 把 PDL 轉變成高層次註釋(p.38)——用 Pascal 的
begin/end或 C 的{}把每行 PDL 包成註釋。「這時,常式的特點已經非常明顯了,設計工作已經結束了,沒看見任何程式碼,但已經知道常式如何工作了。把 PDL 轉換成程式語言程式碼是一件機械、自然、容易的工作。如果你不覺得是這樣,那麼還需要進一步細化 PDL,直到有這種感覺為止。」(p.38–39)
- 在每一行註釋下面填上程式碼(p.39)——
「這有點像給報紙排版。首先畫好輪廓線,然後再把每一篇文章填到空格中,每一個 PDL 註釋行都相當於給程式碼畫的輪廓線,而程式碼相當於文章。」(p.39)
- 書明講註釋要留著,不是刪掉(p.41):「如果是在事後進行註釋,那麼,用兩個註釋行來註釋兩行程式碼就不必要了。但是,採用目前這種方法,注重的是註釋的字面內容而不是它註釋了多少行程式碼。現在,註釋行已經存在了,所以還是將其保留。」「保留了註釋以便提供一個關於程式碼的高層次解釋。」
- 書也順帶指出這個流程的價值(p.41):從 5 行要求定義 → 12 行初始 PDL → 一個較大的常式,「即使要求定義是很詳盡的,常式的建立還是需要在 PDL 和編碼階段進行潛在的設計工作。這種低層次的設計工作正是為什麼編碼不是一件瑣碎事的原因」。
- 非正式地檢查程式碼(p.41)——每填完一塊就「盡力想一下什麼因素可能破壞目前的塊,然後證明這種情況不會發生」;整個常式實現完再停下來檢查一次,因為「某些重大問題在常式實現之前是不會出現的」。
- 進行收尾工作(p.41–42)——六項確認(原文照列):
- 檢查常式的介面:所有輸入輸出資料都作出了解釋、所有參數都用到了(詳見 5.7 節)
- 檢查通用設計品質:只完成一項任務且完成得很好、「與其他常式交叉是控制不嚴的表現」、應該採用了預防錯誤的設計(詳見第 5 章 → 工具-防禦式編程)
- 檢查常式的資料:不精確的變數名、沒有使用的資料、沒有說明的資料
- 檢查常式的控制結構:無限迴圈、不適當的巢狀
- 檢查常式的設計:確認已說明了運算式、參數表和邏輯結構
- 檢查常式的文件:「確認被翻譯成註釋的 PDL 仍然是精確的」,檢查演算法描述、介面假設與非顯式相依的文件資料
- 按需要重複步驟(p.42)——
「如果程式的品質很差,請返回 PDL 階段。高品質程式是一個逐步的過程,所以在重新進行設計和實現活動時,不要猶豫不決。」(p.42)
4.5 檢查常式(p.42–43)
「在設計並實現了常式之後,建立活動的第三個主要步驟是進行檢查」——因為非正式檢查與收尾「不能完全保證」正確性,漏掉的錯誤只會在後面測試時才被發現,那時成本很高(p.42)。
- 在心裡對常式進行查錯處理(p.42)——除了前面的非正式檢查與收尾,另一方法是在心中執行每一個路徑(這個困難「也是造成很難保持常式小規模的原因之一」)。要檢查到每一個規定路徑、中止點與所有例外情況。自己做叫「桌面檢查」;與同事一道做叫「同事評審」「過一遍」或「視察」。
「業餘愛好者與職業程式設計師之間的最大區別就是迷信還是理解……只有 5% 的錯誤是由編譯程式、硬體或者是作業系統引起的(Brown and Sampson 1973;Ostrand and Weyuker 1984)。進入理解境界的程式設計師總是懷疑自己的工作,因為他們知道 95% 的錯誤出自這裡。」(p.42)
「沒有僅僅因為有效便是正確的東西。如果你不知道為什麼它是有效的,那麼往往它是無效的,只不過你沒有發現罷了。」(p.42) - 編譯常式(p.42–43)——書要你刻意晚一點才編譯:
「一旦開始編譯,那麼你腦袋裡的碼錶便開始滴答作響了,在第一次編譯之後,你就開始不停地想:下次編譯一定讓它全對。結果,在這種『就只再編譯一次』的壓力下,做了許多匆忙的、更易產生錯誤的修改,反而浪費了更多的時間。所以,在確信常式是正確的之前,不要急於開始編譯。」(p.42)
編譯時的兩條方針(p.43):把編譯器的警告級別調到最高;消除所有編譯器指出的錯誤和警告的原因(「大量的警告往往意味著程式碼品質不高」)。 - 使用電腦來檢查常式錯誤(p.43)——放進除錯器逐行執行,確保每一行都按預期執行;再用設計階段就想好的測試用例測;必要時搭鷹架(僅測試階段用、不進產品的支撐程式碼)。
- 消除常式中的錯誤(p.43)——
「如果發現程式中的錯誤異乎尋常的多,那就重新開發一個,不要試圖修補它。修補往往意味著不充分的理解,而且肯定會在現在和將來產生更多的錯誤。」(p.43)
🧪 我實際套用的紀錄
- 2026-07-14:(待填)
⚠️ 注意 / 什麼時候不適用
- 偽代碼要描述「做什麼」而非「怎麼用語言寫」——這是書的第 3 條方針「在設計意向這一層次上寫 PDL」(p.31)與小結(p.44):「首先要用易懂的自然語言,避免拘泥於某種程式語言,其次要在意向層次上寫 PDL,描述設計做什麼而不是如何做。」
- 但也不能寫太高(第 4 條方針,p.32):「如果 PDL 寫得太簡略,可能會在編碼過程中忽略問題細節。」判準是「幾乎可以自動生成程式碼」。
- ⛔ 舊卡「保留有價值的偽代碼當意圖說明,其餘刪去」已修正:第一版沒有叫你刪多餘註釋,反而明說「註釋行已經存在了,所以還是將其保留」(p.41)。書唯一的但書在小結:「可以把 PDL 直接翻譯成註釋,但要注意保證註釋是精確而有用的」(p.44)。
- ⛔ 舊卡「回頭檢查、必要時重構」中的「重構」已改寫:第一版第 4 章沒有 refactoring 的概念,它的說法是**「按需要重複步驟……請返回 PDL 階段」**(p.42)與「錯誤異乎尋常的多就重新開發一個,不要修補」(p.43)。
- PDL 不是萬能,也不是唯一(p.33):「PDL 並不是詳細設計的唯一工具。」書只主張它「是有用的工具。不妨試一下」。
隨手用的檢查表(4.5.1「建立子程序」檢查表,p.43–44,原文照列)
- 是否檢查過先決條件已經滿足了?
- 定義常式將要解決的問題了嗎?
- 結構設計是否足夠清楚,使得你可以給常式起個好名字?
- 考慮過如何測試常式了嗎?
- 是否從模組化水平或者滿足時間和記憶體要求角度考慮過效率問題?
- 是否查閱過參考書,以尋找有幫助的演算法?
- 是否用詳盡的 PDL 設計常式?
- 在必要時,是否在邏輯設計步驟前考慮了資料?
- 是否檢查過 PDL,它很容易理解嗎?
- 是否注意到了足以使你返回到結構設計階段的警告(使用了全域資料、更適合其他常式的操作,等等)?
- 是否使用了 PDL 到程式碼流程,是否把 PDL 作為編碼基礎並把原有的 PDL 轉為註釋?
- 是否精確地把 PDL 翻譯成了程式碼?
- 在作出假設時,驗證它們了嗎?
- 是從幾個設計方案中選擇了最好的,還是隨意選擇了一個方案?
- 是否徹底理解你的程式碼?它容易理解嗎?
🔗 相關工具
- 工具-建構的先決條件(同書)——設計常式的第一個步驟就叫「檢查先決條件」(p.34),第 4 章的〈相關章節〉也直接指向第 3 章。專案層級的動手前準備做完了,才輪到單一常式層級的動手前準備。
- 工具-軟體評審與檢查(同書)——PDL 好處第一條是「PDL 可以使評審工作變得更容易……不必檢查原始碼就可以評審詳細設計」(p.33);4.5 節列的「桌面檢查/同事評審/過一遍/視察」(p.42)正是評審卡展開的內容。
- 工具-防禦式編程(同書)——雙向:4.4 收尾工作要求確認常式「應該採用了預防錯誤的設計」(p.41);而 5.6 節反過來把「在編碼前先寫好 PDL」列為優先於防錯性編程的預防手段(p.61)。
- 工具-程式碼調校(同書)——4.3「考慮效率」是這條原則在常式層級的第一次出現:「不必浪費精力去考慮個別常式的執行效率。最佳化的收益主要來自高層次設計……除非必要,不要浪費時間進行增量改進」(p.35)。要調校時再去看調校卡的量測紀律。
- 工具-專案規模的影響(同書)——第 4 章自陳「本章的中心內容是如何編寫小規模的程式,以及編寫對各種規模專案都十分關鍵的程式的特定步驟」(p.31);這套步驟本身不隨規模改變,但套用時的正式程度與規模有關,見規模卡。
- 工具-一次只做一件事(編寫可讀程式碼的藝術)——對照:都是「先把意圖切乾淨、再寫語法」。
- 工具-設計訣竅(HtDP 的系統化設計)——對照:另一套「先寫合約與範例、再填本體」的模板化設計流程。
- 回到來源:程式碼大全(第一版,第 4 章「建立子程序的步驟」,p.31–44)