概述#

程式設計師誓言中有數項承諾與**傷害(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)。

軟體有兩個價值:

  1. 行為價值(Behavior):軟體目前做了什麼
  2. 結構價值(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):任何人在任何時間都能執行測試,確認系統正常運作;理想上每天執行多次

你欠客戶、雇主、隊友、業務分析師、測試人員和專案經理這個承諾。但最重要的是,你欠自己——因為如果你無法證明你完成的工作就是你被付錢去做的工作,你怎能稱自己為專業人員?