概述#
程式設計師誓言中有數項承諾與**傷害(Harm)**直接相關。本章探討三項核心承諾:不產出有害的程式碼、永遠交出最好的作品、以及提供可重複驗證的正確性證明。這些承諾涵蓋了對社會、對功能、對結構的責任,並從 Dijkstra 的結構化程式設計思想,延伸到以 TDD 作為現代軟體正確性證明的實踐方法。
首先,不造成傷害(First, Do No Harm)#
承諾 1:我不會產出有害的程式碼。
軟體專業人員的首要承諾就是不造成傷害。你的程式碼不得傷害使用者、雇主、管理者或同事。你必須知道你的程式碼做了什麼、確認它能正確運作、並確保它是乾淨的。
對社會的傷害(No Harm to Society)#
- Volkswagen 排放醜聞:程式設計師撰寫了刻意欺騙 EPA 排放測試的程式碼,導致車輛排放超標 20 倍的有害氮氧化物。不論他們是否知情,他們都應該知道自己程式碼的用途——躲在別人撰寫的需求背後不是藉口
- HealthCare.gov 失敗上線:2013 年 10 月 1 日的災難性啟動,幾乎導致整個公共政策被推翻。每一位知道系統尚未就緒卻保持沉默的程式設計師、主管、經理都負有責任
你被聘為程式設計師的首要原因之一,是因為你有能力在問題發生前辨識出它。因此,你有責任在災難發生前開口說出來。
對功能的傷害(Harm to Function)#
你必須知道你的程式碼能正確運作,不會對公司、使用者或同事造成傷害。
- Knight Capital 事件(2012):技術人員在 8 台伺服器中只更新了 7 台,第 8 台仍運行舊版本。舊版已停用的 Power Peg 演算法因為一個被重新賦予用途的旗標而意外啟動,在 45 分鐘內進行了高速無限迴圈交易,非自願購入超過 70 億美元的股票,最終虧損 4.6 億美元,而公司現金僅 3.6 億——45 分鐘,一個愚蠢的錯誤,破產
- Toyota 暴衝事件:軟體導致車輛無法控制地加速,可能造成多達 89 人死亡。調查發現系統中有超過一萬個全域變數
風險與知識的關係:當利害關係越高,你就越需要將知識推向完美。如果關乎人命,你必須知道你的程式碼不會殺人。如果關乎鉅款,你必須知道你的程式碼不會造成損失。而事實上,幾乎總是有比你想像的更多東西處於風險之中。
對結構的傷害(No Harm to Structure)#
你不得傷害程式碼的結構,必須保持程式碼的整潔和良好組織。
- Knight Capital 的災難根因:程式設計師忘記了已停用的 Power Peg 程式碼仍然存在於系統中,也忘記了被重新賦予用途的旗標會啟動它
- Toyota 的根因:超過一萬個全域變數,使得沒有人能真正理解軟體的行為
- 結構混亂 = 有害軟體:結構越糾結,就越難知道程式碼會做什麼;越混亂,不確定性就越高
快速且骯髒的修補(quick and dirty patch)在生產環境危機中可以使用——能解決問題的笨方法不是笨方法。但你不能讓這個修補長期留在程式碼中,否則它就會開始造成傷害。
結構性傷害是指任何使原始碼難以閱讀、難以理解、難以修改或難以重用的東西。每位專業軟體開發人員都有責任了解良好軟體結構的紀律和標準。
軟體之所以為「軟」#
software 的第一個字是 soft——軟體被設計為容易修改。如果我們不希望它容易修改,我們會稱它為硬體(hardware)。
軟體有兩個價值:
- 行為價值(Behavior):軟體目前做了什麼
- 結構價值(Softness):軟體有多容易被修改
哪個價值更重要? 想像兩個程式:
- 程式 A:完美運作但無法修改 → 需求一變就永遠無用
- 程式 B:什麼都做不對但容易修改 → 可以被改到正確,並持續運作
因此,除了最緊急的情況外,結構價值應優先於行為價值。
flowchart TD
S["軟體的兩個價值"] --> B["行為價值(Behavior)"]
S --> ST["結構價值(Softness)"]
B --> A["程式 A:完美運作但無法修改"]
A --> A1["需求一變 → 永遠無用"]
ST --> PB["程式 B:目前不正確但容易修改"]
PB --> PB1["可修改到正確"]
PB1 --> PB2["持續運作"]
A1 --> C["結論:結構價值優先於行為價值"]
PB2 --> C新創公司不是需要你犧牲軟體彈性的緊急情況。事實上恰恰相反——新創公司唯一確定的事,就是你正在打造錯誤的產品。沒有任何產品能在與使用者接觸後毫髮無傷。如果你無法在不製造混亂的情況下修改它,你就完蛋了。
「When it comes to software, it never pays to rush.」——Brian Marick
測試(Tests)#
- 測試優先:先寫測試、先清理測試
- 沒有測試證明程式碼能運作,就無法防止行為傷害
- 沒有測試允許你清理程式碼,就無法防止結構傷害
- 沒有遵循 TDD 三法則,就無法保證測試套件的完整性
TDD 正在成為專業軟體開發人員的最低標準。我們撰寫讓整個世界運作的規則——社會中幾乎沒有任何日常活動不涉及軟體。客戶和使用者終將要求我們遵守這樣的紀律。
最佳作品(Best Work)#
承諾 2:我產出的程式碼永遠是我最好的作品。我不會明知故犯地讓行為或結構上有缺陷的程式碼累積。
Kent Beck 曾說:「First make it work. Then make it right.」
讓程式運作只是第一步,也是最簡單的一步。第二步——清理程式碼——才是更困難的。太多程式設計師在程式能運作後就認為自己完成了,留下一堆糾結不可讀的程式碼拖慢整個團隊。
趕工的心理根源#
- 程式設計師認為自己的價值在於速度
- 他們知道自己薪水很高,所以覺得必須在短時間內交付大量功能
- 但軟體本來就很難、需要很多時間,於是他們覺得自己太慢了、覺得自己在失敗
- 這種壓力使他們趕工——趕著讓程式運作,然後宣告完成
- 真正的壓力來自內心,而非老闆:我們把開發速度視為自我價值的衡量
讓它正確(Making It Right)#
- 結構價值比行為價值更重要,因為軟體系統必須能應對需求變化才有長期價值
- 需求在專案初期(使用者首次看到功能運作後)最容易變動
- 從一開始就需要乾淨的結構,否則連第一個版本都會被混亂拖慢
結構與行為的關係:好的結構促成好的行為,壞的結構阻礙好的行為。行為的價值嚴重依賴結構,因此專業開發人員應將結構優先於行為。
什麼是好的結構?#
好的結構使系統容易測試、容易修改、容易重用:
- 修改一部分不會破壞其他部分
- 修改一個模組不會強制大規模重新編譯和重新部署
- 高階策略與低階細節保持分離且獨立
壞的結構產生三種設計異味(Design Smells):
| 設計異味 | 說明 |
|---|---|
| 僵化性(Rigidity) | 相對小的變更導致大範圍重新編譯、重建、重新部署 |
| 脆弱性(Fragility) | 小的行為變更迫使大量模組對應修改,造成改一處壞他處的高風險 |
| 不可移動性(Immobility) | 模組中的有用行為與現有系統糾纏太深,無法抽取至新系統中使用 |
造成這些設計異味的根因是原始碼依賴關係(Source code dependencies)。解決方案是依賴管理(Dependency management),工具則是 SOLID 原則。系統的整體價值取決於 SOLID 原則的正確應用。
Eisenhower 矩陣#
Eisenhower 曾說:「我有兩種問題,緊急的和重要的。緊急的不重要,重要的從不緊急。」

Figure 12.1: Eisenhower's decision matrix
優先順序排列:
| # | 類別 | 處理方式 |
|---|---|---|
| 1 | 重要且緊急 | 最優先處理 |
| 2 | 重要但不緊急 | 第二優先 |
| 3 | 不重要但緊急 | 不應該做(浪費) |
| 4 | 既不重要也不緊急 | 不應該做 |
關鍵洞見:
- 緊急性關乎時間(短期),重要性關乎價值(長期)
- 結構是長期的,因此是重要的
- 行為是短期的,因此只是緊急的
- 結構優先,行為其次——你的老闆可能不同意,但維護結構不是你老闆的工作,是你的
如何調和 “先讓它運作” 與 “結構優先”? 將問題拆分成極小的單位——不是 User Story(太大),而是測試。先讓一個測試通過,然後修正結構,再寫下一個測試。這就是 TDD 的 Red → Green → Refactor 循環的道德基礎。
程式設計師是利害關係人(Stakeholders)#
- 專案的成功直接影響你的職涯和聲譽,你就是利害關係人
- 作為利害關係人,你有權對系統的開發和結構方式發表意見
- 作為工程師,你有責任確保系統不會因為壞的行為或壞的結構造成傷害
- 如果老闆要你忽略結構只專注行為,你應該拒絕
你的老闆懂 SOLID 原則嗎?懂設計模式嗎?懂依賴反轉嗎?懂 TDD 嗎?如果不懂,那結構的維護就是你的責任,而非他的。大多數管理者期望在他們需要的事情上有所爭取,他們尊重願意為正確的事情而戰的人。
你的最佳作品(Your Best)#
- 這項承諾並非永遠黑白分明——有時結構必須向時程妥協
- 你可以為了趕上展覽會而做快速修補,也可以在結構「接近但不完美」時出貨
- 但承諾的核心是:你會在加入更多行為之前先處理那些行為和結構的問題,不讓缺陷累積
書中以一段老闆與程式設計師的對話,展示了如何專業地堅持立場:面對老闆威脅開除,程式設計師仍然堅持必須先清理結構才能繼續開發新功能。最終老闆被說服,因為程式設計師展現了勇氣和專業判斷。
可重複的證明(Repeatable Proof)#
承諾 3:我會在每次發布時,提供快速、確實且可重複的證明,證明程式碼的每個元素都能正確運作。
Dijkstra 與正確性證明#
Edsger Wybe Dijkstra(1930 年生於鹿特丹)是荷蘭第一位程式設計師(1952 年)。他在 1955 年得出結論:程式設計的智識挑戰大於理論物理,因此選擇程式設計作為終身職業。他的導師 van Wijngaarden 告訴他,他可能就是那個將程式設計變成科學的人。
Dijkstra 認為軟體是一種形式系統(formal system),類似數學。他著手建立軟體證明的語言和紀律,提出三種證明演算法正確性的技術:
| 技術 | 說明 |
|---|---|
| 列舉(Enumeration) | 證明序列中的語句或由布林表達式選擇的語句是正確的 |
| 歸納(Induction) | 證明迴圈是正確的 |
| 抽象(Abstraction) | 將語句群組拆分成更小的可證明區塊 |

Figure 12.3: Handwritten proof of the algorithm
然而,這種方法極其困難。Dijkstra 自己也承認,如果要求程式設計師為每個簡單迴圈都提供這樣的證明,那就永遠寫不出有實際規模的程式。他希望透過建立定理庫來使證明更實用,但他未能預見軟體會變得如此普遍——所需的定理庫將遠超任何人類所能掌握。
結構化程式設計(Structured Programming)#
1968 年,Dijkstra 發表了著名的 “Go To Statement Considered Harmful” 一文。GOTO 之所以有害,是因為它破壞了證明正確性所依賴的基礎:
- 列舉要求每個語句有單一進入點和單一退出點
- 歸納是列舉的特殊形式
- GOTO 可以跳入或跳出列舉序列的中間,使得列舉和歸納都變得不可行
Dijkstra 建議程式碼應由三種標準構建塊組成:
| 構建塊 | 說明 |
|---|---|
| 序列(Sequence) | 依時間排序的非分支程式碼 |
| 選擇(Selection) | 由述詞選擇的 if/else、switch/case |
| 迭代(Iteration) | 由述詞控制重複的 while、for 迴圈 |
任何程式,無論多複雜,都可以僅由這三種結構組成,而這樣的程式是可證明的。即使我們不撰寫形式化證明,可證明意味著可推理;不可證明意味著不可推理,也就無法正確測試。
flowchart TD
subgraph 序列["序列(Sequence)"]
direction TB
SA["A"] --> SB["B"] --> SC["C"]
end
subgraph 選擇["選擇(Selection)"]
direction TB
Cond{"條件判斷"}
Cond -->|是| Y["分支 A"]
Cond -->|否| N["分支 B"]
end
subgraph 迭代["迭代(Iteration)"]
direction TB
Loop{"述詞成立?"}
Loop -->|是| Body["執行主體"]
Body --> Loop
Loop -->|否| Exit["離開迴圈"]
end
序列 --> R["三者組合"]
選擇 --> R
迭代 --> R
R --> Provable["可證明的程式"]功能分解(Functional Decomposition)#
結構化程式設計帶來了一個意外的副產品:功能分解——從程式的最頂層開始,遞迴地將其拆分成越來越小的可證明單元。這一思想在 1970-80 年代由 Ed Yourdon、Larry Constantine、Tom DeMarco 等人推廣為結構化分析與設計。
TDD 就是功能分解#
- TDD 的 Red → Green → Refactor 循環本質上就是功能分解
- 你必須將問題分解成可測試的小元素,寫針對這些小元素的測試
- 結果是:每個用 TDD 建構的系統都是由功能分解的元素組成,符合結構化程式設計,因此是可證明的
- 測試就是證明——或者更準確地說,測試就是理論(theory)
軟體是科學,不是數學#
Dijkstra 認為軟體是一種數學,希望建立由公設、定理、推論和引理組成的上層結構。但我們認識到,軟體其實是一種科學:
- 科學以實驗來驗證理論
- 我們以通過的測試來建立理論的上層結構
- 我們沒有數學證明演化論、相對論或大爆炸理論,但每次你上車或搭飛機,你都在用生命押注牛頓運動定律是正確的
- TDD 給我們的正是這種證明:不是形式化的數學證明,而是經驗性的實證證明——我們每天都依賴的那種證明
timeline
title 從 Dijkstra 到 TDD 的思想演進
1952 : Dijkstra 成為荷蘭第一位程式設計師
1968 : 發表 "Go To Statement Considered Harmful"
1970s : 結構化程式設計興起
1980s : 結構化分析與設計(Yourdon、DeMarco 等)
1990s : 功能分解(自上而下設計)
現代 : TDD — 科學方法取代數學證明承諾 3 的具體含義:
- 快速(Quick):測試套件應在數分鐘內完成,而非數小時
- 確實(Sure):測試套件通過時,你就知道可以出貨
- 可重複(Repeatable):任何人在任何時間都能執行測試,確認系統正常運作;理想上每天執行多次
你欠客戶、雇主、隊友、業務分析師、測試人員和專案經理這個承諾。但最重要的是,你欠自己——因為如果你無法證明你完成的工作就是你被付錢去做的工作,你怎能稱自己為專業人員?