🎯 什麼情境該想到我

當你被催著「先開始寫再說」,但問題定義、需求、結構設計其實還沒到位時——這張卡是用來在動手前擋下來,並且用成本數字說服你的老闆為什麼要擋。

書裡的比喻:如果基礎打不好,你最多只能防止失敗,談不上做好。

「如果你想做一件精美的首飾,那麼就得用鑽石作原料。如果你用的是磚頭,那你所能得到的最好結果不過是塊漂亮的磚頭而已。」(p.12)

上游沒做好,下游再認真也救不回來——書用「軟體食物鏈」講這件事:結構設計吃掉需求分析、詳細設計以結構設計者為食、而他自己又成為編碼者的食物;需求定義一旦被污染,程式設計師是食物鏈最後一環,吃下所有上游的毒(p.14–15)。


⚙️ 怎麼用

步驟 0:先把成本數字準備好(這是你唯一的談判籌碼)

動手前先記住這四組數字,被要求「馬上開始寫」時直接拿出來:

主張倍數出處
在需求定義/結構設計階段改,vs 在建構與維護階段改成本低 50 到 100 倍Boehm 和 Pappecio,1988(p.15)
在詳細設計、編碼或單元測試階段消除錯誤,vs 在系統測試與功能強化階段成本低 10 到 100 倍Fagan,1976(p.15)
一個需求錯誤,在後面各階段才修正的放大倍數總體結構 、編碼 10×、單元或系統測試 20×、驗收測試 50×、維護 100×IBM、GTE、TRW(p.17)
全書小結版本建構工作後更改需求,比在需求分析階段改高 20 到 100 倍p.30

錯誤引入時間 vs 發現時間的相對修復成本(表 3-1,Robert Dunn 總結,p.15):

錯誤發現時間 \ 引入時間需求分析細節設計編碼
需求分析1
細節設計21
波動測試521
結構測試1552
功能測試25105

「在需求分析階段引入的錯誤,如果馬上發現並消除所耗費的成本是 1000 美元的話,那麼如果到了功能測試階段才發現和消除,耗費的成本則會高達 25000 美元。」(p.15)

一句話版本:一旦引入錯誤,就盡早發現和消除它。錯誤在軟體食物鏈中存留的時間越長,危害傳播得越遠(p.15)。

步驟 1:問題定義關(p.16)

  • 有一份簡短的問題說明,而且它聽起來像一個問題,不像一個解決方案
    • 好:「我們無法跟上指令系統」。壞:「我們需要最佳化資料入口系統以便跟上指令系統」。
  • 從使用者觀點、用使用者的語言寫的,沒有用電腦技術術語(除非問題本身就是關於電腦,例如編譯太慢)。
  • 確認過最好的解法「可能根本不是一個電腦程式」——書裡的例子:與其寫一支年度利潤報表程式,不如讓祕書把四季利潤用計算機加起來。
  • 沒過的代價是雙重的:你浪費時間解了一個錯的問題,而真正的問題仍然沒解決。

步驟 2:需求關(p.16–19)

先確認為什麼要有正式需求(p.16–17):讓使用者而不是程式設計師決定系統功能、避免爭議(吵起來就查文件)、把開發開始後的改動降到最小。

拿書中的檢查表逐條問(p.19,摘關鍵幾條):

內容

  • 所有輸入都定義了嗎?含來源、精度、取值範圍、頻率。
  • 所有輸出都定義了嗎?含目標、精度、取值範圍、頻率、格式。
  • 所有硬體/軟體介面、通訊介面(含握手、錯誤檢查、通訊約定)都定義了嗎?
  • 從使用者觀點定義了必要操作的反應時間嗎?處理時間、資料傳輸率、系統吞吐能力呢?
  • 保密級別、可靠性(含出錯後果、要保護的關鍵資訊、錯誤測試與恢復策略)規定了嗎?
  • 所需最大記憶體、最大儲存容量規定了嗎?
  • 維護性規定了嗎(對運行環境、精度、效能、與其他軟體介面變化的適應能力)?
  • 相互衝突的設計之間的折衷原則規定了嗎(例如堅固性 vs 準確性)?
  • 制定了系統成敗的標準嗎?

完善性

  • 開發開始前暫時得不到的資訊是什麼?不夠完善的區域有標示出來嗎?
  • 需求中有沒有哪一部分讓你不安?有沒有根本不可能實現、只是為了取悅老闆和使用者才加進來的內容?

品質

  • 是用使用者的語言寫的嗎?使用者也這樣認為嗎?
  • 條目之間避免衝突了嗎?有沒有不小心規定了設計工作
  • 每一條都能追溯到產生它的環境嗎?
  • 每一條需求都可以作為測試依據嗎?可以針對每一條獨立測試嗎?
  • 對可能的改動作出規定了嗎(含每項改動的可能性)?

步驟 3:結構設計關(p.20–26)

為什麼它是先決條件:結構設計的品質決定系統概念上的完整性,而這又決定最終品質。在建構階段修結構錯誤,比修需求錯誤省時,但比修編碼錯誤耗時得多(p.20)。

逐項確認結構設計有沒有交代(p.20–25):

  • 程式的組織形式:有總體概括描述;主要模組都定義了(模組 ≠ 常式,是完成某一高階功能的常式組合);需求中列出的每一項功能都至少有一個模組覆蓋;每個模組做什麼、模組間介面、誰可以呼叫誰、傳送什麼資料,都明確定義。並且說明了為什麼選這個組織形式(考慮過的替代方案)——設計理由對維護性和設計本身同樣重要(Rombach 1990)。
  • 變動策略:說明系統如何應付變動;最可能的變動同時是最容易實現的變動;每一個單一變動只涉及數量有限的幾個模組;有延緩變動的手段(例如用表驅動而非手工編碼、把表放外部檔案而非編碼在程式裡,改了不必重新編譯)。
  • 購買而不是建造的決定:要用現成軟體就說明如何讓它適應新需求,並證明改得動。(Boehm 1984:重用舊軟體是提高生產率的首要因素;Caper Jones 1986:購買的程式碼從 0 上升到 50%,生產率可提高一倍。)
  • 主要資料結構:列出使用的主要檔案、表、資料結構,附替代方案與選擇理由;不允許一個以上的模組直接存取資料結構,除非透過存取常式;用資料庫就規定其組織形式與內容;遵循資料守恆定律——每一個進入的資料都應該出去,不出去就沒必要進來。
  • 關鍵演算法:描述或指出演算法,說明考慮過哪些方案、為什麼選它、基於什麼假定(書例:為什麼用堆排序而不是快速排序或插入排序)。
  • 主要物件(物件導向系統):規定每個物件的責任、彼此如何互動,含排序層次、狀態轉換、物件一致性。
  • 通用功能:使用者介面(命令結構、輸入格式、選單;且要模組化到可以把互動式介面換成批次介面,這在單元與子系統測試特別有用)、輸入/輸出(查詢方式;在區域/記錄/檔案哪個層次檢查 I/O 錯誤)、記憶體管理(正常與極端情況的估計)、字串儲存(估計字串佔用的記憶體;壓縮;不改程式碼即可維護字串;譯成外文時對程式碼影響最小)。
  • 錯誤處理策略(p.23–24)——書說有人估計程式中 90% 的程式碼是為了應付例外的錯誤處理或內務處理,只有 10% 在處理正常情況,所以這一項一定要在結構階段講清楚。五個必答問題:
    1. 錯誤處理是糾正還是僅僅測試錯誤?(無論哪種,都要提醒使用者)
    2. 錯誤測試是主動的還是被動的? 主動=積極預防,例如檢驗使用者輸入是否合法;被動=無法迴避時才反應。
    3. 程式怎樣對付錯誤?立刻拋棄出錯資料、進入錯誤處理狀態、還是全部處理完再通知使用者?
    4. 處理錯誤訊息的約定是什麼?沒有統一策略,使用者介面就會像迷宮一樣忽東忽西。
    5. 在哪一層次處理錯誤?發現處就地處理、交給錯誤處理常式、還是交給更高層常式?另外:每個模組驗證輸入合法性的責任級別有多高?
  • 堅固性(robustness)(p.24):
    • 裕度設計(over-engineering):明確表述要求的系統裕度有多大。結構設計規定的裕度往往比需求定義大,因為系統由許多部分組成——軟體鏈條的強度不是由最薄弱的一環決定,而是由所有薄弱環節的乘積決定。講清楚裕度級,才不會有的地方裕度過大、有的過小。
    • 斷言(assertions):結構中要規定斷言的使用程度。斷言是放在程式碼中、執行時可使其自檢的可執行語句。

      「比如,系統假定使用者資訊檔案永遠不會超過 5000 記錄行,那麼程式中可能會包含一段說明這個假定的斷言。只要這個檔案不超過 5000,那麼斷言就保持沉默,而一旦斷言發現此檔案超過了 5000 個記錄行,那它就會聲稱已發現了一個錯誤。」(p.24)
      「為了在程式中加入斷言,你必須知道在設計系統時所做的假設,這也是在結構設計中應闡明採用假設的原因之一。」(p.24)

    • 容錯性(fault tolerance):指明期望的容錯類型——返回並重新開始、用輔助程式碼代替基本程式碼、投票演算法(三種方法各算一次再比較)、用假想值代替錯誤結果;其他還有部分運行、功能降級、關閉自己、自動重新開始。
  • 效能:目標含速度與記憶體使用;結構設計要估計並解釋為什麼這些目標可以達到;規定每一個模組的時間與儲存空間預算。
  • 整體品質:概念完整性是最重要的(Brooks 1975《The Mythical Man-Month》);看到它應該為方案的自然和簡單而折服;每個決定的動機都要說清楚(當心「我們過去一直是這麼幹的」);恰好落在過分定義與定義不足的分界線上;沒有任何部分是只為取悅老闆而加的。

步驟 4:語言選擇關(p.26–27)

  • 確認團隊對這個語言的熟悉度。TRW 資料:同水平的兩人,一個用了三年的語言 vs 陌生語言,前者效率高 30%;IBM 調查:對某語言經驗豐富者,效率是無經驗者的 三倍(Walston 和 Felix 1977)。
  • 確認語言層級。Brooks 1987:Pascal 和 Ada 的效率、可靠性、簡單性、可懂性是組合語言與機器語言等低階語言的 5 倍
  • 小心「用 A 語言寫 B 語言的程式」——書中的典型故事:一群 Fortran 出身的人用 Pascal 編譯器寫出變形的 Fortran,用 goto 和全域資料歪曲了 Pascal,卻把 Pascal 豐富的控制和資料結構棄之不用(Hanson 1984、Yourdon 1986)。

步驟 5:編程約定關(p.29)

  • 在建構工作開始之前,一定要寫明將採用的編程約定(變數與常式命名、格式約定、註釋約定),而且要寫得非常詳盡,使得在編程過程中無法對其進行改動。
  • 理由(p.30 小結):在建構工作結束後再改程式碼來滿足約定,幾乎是不可能的。

步驟 6:時間預算關(p.29–30)

  • 排程裡有沒有先決條件的時間?「一個運行良好的專案通常把 20~30% 的時間用於先決條件」——而且這 20~30% 不包含詳細設計,因為詳細設計是建構活動的一部分。
  • 需求不穩定又被要求報時程時:先做完需求分析,再估計專案其餘部分需要多少時間

    「在建築中,在知道要建什麼之前,就進行工程預算顯然是荒謬的。」(p.30)

步驟 7:如果檢查沒過,這樣處理(p.17–18)

  1. 停下來回去補。「如果你在開車從芝加哥到洛杉磯的途中,發現自己到了紐約市郊,那麼停下車來看一下地圖是浪費時間嗎?」
  2. 讓每個人知道變更需求的代價。標準台詞:「你的想法不錯,但由於它不在需求文件之中,我想先做一個變動後的進度和成本估計,然後我們再決定是立刻採用還是以後再說。」——「時間進度」和「成本」這兩個詞往往比咖啡和潑冷水更管用,會把許多「立刻採用」變成「最好採用」。
  3. 建立一套更改控制過程(正式的變更審查委員會)。使用者改主意很正常,改到你跟不上才不正常。
  4. 用開發方法容納變動:原型化開發、漸進開發(按階段發布、短週期、拿回饋再調整)。
  5. 放棄專案。即使不能真的砍掉,也想一想砍掉會有什麼差別。

被老闆逼著立刻開始編碼時(WISCA/WIMP 現象:「為什麼 Sam 沒有正在寫程式?」)的四個替代做法(p.13–14):平靜地拒絕按錯誤順序工作/假裝在寫程式(把舊的程式清單放桌角,埋頭寫需求與構想文件)/教育老闆技術專案的開發方式/另找一份工作。


🧪 我實際套用的紀錄

  • (待填)

⚠️ 注意 / 什麼時候不適用

  • 這章不教你怎麼做需求分析或結構設計,只教你怎麼確認它們做完了、品質夠不夠當建構的基礎。要學怎麼做,書中推薦 DeMarco、Yourdon、Hatley & Pirbhai、Shlaer & Mellor、IEEE Std 830-1984(p.19–20)。
  • 「穩定的需求」是神話(p.17)。IBM 調查:一個典型的一百萬字需求分析,開發過程中約 25% 的內容要變動。使用者是隨專案進行才逐漸弄清自己要什麼的——「一個從不變更需求的計畫,事實上是一個對使用者的需求不予理睬的計畫。」 所以本卡的目的不是凍結需求,是讓變動發生在最便宜的階段、並且被控制
  • 成本倍數在小專案會縮水:「在較小規模的計畫中,在維護階段修正錯誤的放大因子可能是 20 而不是 100,因為這時管理費用較低。」(p.17)
  • 先決條件隨專案規模與正式程度而變(3.8,p.30)。書中兩張檢查表都明說:非正式專案裡有些條款根本不必考慮,大型正式專案才建議逐條仔細考慮(p.18、p.25)。細節見書中第 21 章。
  • 「好的結構應與機器和語言獨立」有例外:如果程式專門是為某一機型或語言設計的,這條不適用(p.25)。
  • 如果你已經很熟軟體工程生存期循環,書自己說可以跳過這章直接進下一章(p.12)。
  • 也不要走到另一個極端:結構設計應該「恰好在過分定義和定義不足的分界線上」,不能以犧牲某一部分為代價來重視另一部分(p.25)。

🔗 相關工具