1968-結構化程式與GoTo#
案件摘要#
1968 年 3 月,Edsger Dijkstra 在 Communications of the ACM 發表一封只有一頁半的信:"Go To Statement Considered Harmful"。一句話引爆了持續十年的論戰,也永久改變了程式語言的設計方向。這封信不是憑空起訴——它背後有 1966 年 Böhm 與 Jacopini 的數學定義作為物證,有 Dijkstra 在 ALGOL 60 編譯器實作中累積的現場證據。本案的真相是:goto 不是語法問題,而是「程式的推理能力」問題。
前因 -- 為什麼會有這個案子#
- goto 滿天飛的年代:早期程式(FORTRAN、組合語言風格)以 goto/label 跳來跳去,程式像一團義大利麵——控制流沒有任何層次結構,閱讀者必須在腦中同時追蹤所有可能的跳躍路徑。
- Dijkstra 的現場經驗:1960 年 Dijkstra 參與實作第一個 ALGOL 60 編譯器,親身體會區塊結構(block structure:begin...end 配對、變數作用域層層嵌套)讓程式可以「一塊一塊局部地證明正確」。他意識到:跳躍會打碎這種局部性。
- 1966 年的物證:Böhm 與 Jacopini 發表定義性證明——任何流程圖都可以只用順序、選擇、迴圈三種結構表達。這意味著 goto 在表達能力上是多餘的:沒有它寫不出的程式。
- 軟體危機醞釀:1960 年代後期專案規模暴增、除錯成本失控,業界開始尋找「寫對程式」的方法學,Dijkstra 的信正好踩在引爆點上。
線索與推理 -- 數學式、程式、理論#
Böhm–Jacopini 定義:三種結構夠用#
設任一流程圖程式為帶標號語句的序列,Böhm 與 Jacopini(1966, Calcolo)證明:存在等價程式僅用
- 順序(sequencing): \(S_1;\ S_2\)
- 選擇(selection): \(\text{if } b \text{ then } S_1 \text{ else } S_2\)
- 迴圈(iteration): \(\text{while } b \text{ do } S\)
他們的證明技巧是:引入一個狀態變數模擬 goto 的跳躍目標。每個標號 \(L_i\) 對應一個狀態值,跳躍變成「設定狀態變數再落入統一迴圈」:
# goto 程式:
# L1: S1; if b goto L3; S2; goto L1; L3: S3
# 等價結構化改寫(Böhm–Jacopini 技巧):
state = 1
while state != 0:
if state == 1:
S1()
state = 3 if b() else 2
elif state == 2:
S2()
state = 1
elif state == 3:
S3()
state = 0
程式等價性可用不變式推理(invariant reasoning)描述:結構化程式的每個迴圈頭都對應一個不變式 \(I\) ,正確性證明即驗證
(Hoare 邏輯,1969,正是建立在結構化控制流之上——goto 程式無法這樣局部地標注與驗證。)
Dijkstra 的起訴書核心論證#
信中(原文如此改述)的推理鏈: 1. 程式員的主要心智負擔是「在程式執行的靜態文本與動態程序之間,保持對應」。 2. goto 的跳躍目標數量 \(G\) 使這個對應的衡量指標隨之膨脹:
而結構化程式中,每個結構的進出口都是單一且可預測的,負擔是常數。 3. 因此 goto 應從高階語言中廢除——「我們的智力有限,必須節約使用」。
goto 程式 vs 結構化程式:實際對照#
同一個「找到第一個滿足條件的元素」任務:
/* goto 版(1960 年代風格) */
int i = 0;
loop:
if (i >= n) goto notfound;
if (cond(a[i])) goto found;
i++;
goto loop;
notfound:
i = -1;
found:
return i;
/* 結構化版:控制流單進單出,可局部驗證 */
int find(int a[], int n) {
for (int i = 0; i < n; i++)
if (cond(a[i])) return i; /* 進出口清楚 */
return -1;
}
兩者邏輯相同,但結構化版的控制流圖是一條簡單的鏈,goto 版是帶環的網狀圖。Dijkstra 的主張由此可驗:程式是寫給人推理的,機器只是順帶執行。
爭論的火花#
反方(如 Donald Knuth 1974 年 "Structured Programming with go to Statements")指出:某些效能關鍵情境(迴圈逸出、狀態機)goto 仍是合理工具;正方強調預設紀律。最終的判決是折衷的現實:結構化為原則,goto 為例外。
結案 -- 後果與影響#
- 語言設計轉向:1970 年 Pascal 幾乎不用 goto(Wirth 保留它但置於語言邊緣、需先行宣告標號);C 雖保留 goto 但 K&R 全書僅示範少數場景;Java、Python 乾脆取消 goto。
- 方法學誕生:結構化程式設計成為 1970–80 年代軟體工程的教科書標準;Dijkstra 的「正確性優先」思想孕育了 Hoare 邏輯、形式驗證、以及今日的測試與型別紀律。
- 編譯器影響:結構化控制流讓編譯器能建立支配樹(dominator tree)與結構化中間表示,最佳化與資料流分析因此大幅簡化。
- 歷史地位:這封一頁半的信是史上被引用最多的電腦科學文獻之一,"considered harmful" 甚至成為標題文化的固定句式。
關鍵人物與文獻#
- Edsger W. Dijkstra:1968 "Go To Statement Considered Harmful," CACM 11(3), 147–148;1972 圖靈獎。
- Corrado Böhm & Giuseppe Jacopini:1966 "Flow diagrams, Turing machines and languages with only two formation rules," Calcolo 3(2), 363–371。
- 文獻:
- C. A. R. Hoare, "An Axiomatic Basis for Computer Programming," CACM 12(10), 1969.
- D. E. Knuth, "Structured Programming with go to Statements," Computing Surveys 6(4), 1974.