1968 GOTO 有害論:一封信引發的軟體革命#
案發現場#
1960 年代末,電腦工業撞上了第一堵真正的牆。硬體按摩爾定律突飛猛進,但軟體的開發卻頻頻失控:IBM 的 OS/360 作業系統動員上千名程式設計師、花費數億美元,推出後仍 bug 疊出;NATO 於 1968 年在德國加米施(Garmisch)召開會議,首次正式使用了「軟體危機」(software crisis)一詞。專案延期、預算爆炸、品質崩壞——程式越寫越大,混亂卻呈指數成長。
混亂的元凶在哪裡?追查現場可以發現一個共同特徵:程式碼裡佈滿 GOTO。BASIC(見 1964-BASIC.md)與組合語言的程式,控制流像一團義大利麵:第 100 行跳到第 50 行,第 50 行跳到第 900 行,條件跳躍再跳回來。閱讀程式的人無法「從上到下」理解執行順序,任何修改都可能牽一髮動全身。
遇見這個問題的核心人物是荷蘭電腦科學家 Edsger W. Dijkstra——他正是 ALGOL 60 報告的實作先驅(第一個 ALGOL 60 編譯器的實作者)、堆疊式執行模型的提出者。1968 年 3 月,他在《Communications of the ACM》上發表了一封只有一頁多的信,標題是編輯 Bruce Gilmour 建議的 「Go To Statement Considered Harmful」(GOTO 敘述有害論)。這封信被公認為程式設計史上影響最大的單一出版物。
偵查過程#
線索一:追蹤程序的「動態過程」為何痛苦#
Dijkstra 的推理從一個認知科學式的觀察出發:人類心智善於靜態地理解空間關係,卻拙於在腦中追蹤動態的時間過程。他指出,程式設計師的智能力量主要用於「想像程式執行時狀態的變化」;而 GOTO 的罪惡不在語法層面,而在語義層面——它讓「程式的靜態文本」與「執行的動態過程」失去對應關係:
100 IF X > 0 THEN GOTO 300
200 LET Y = Y + 1
210 GOTO 500
300 LET Y = Y - 1
310 IF Y < 0 THEN GOTO 200
400 PRINT Y
410 GOTO 100
500 REM ...
讀這段程式,你必須在腦中同時追蹤多個跳躍目標與回傳點,執行順序無法從文本的幾何結構中讀出。Dijkstra 推導出的結論是:程式的品質與「靜態文本的可理解性」成正比,而 GOTO 正是摧毀這種對應的元凶。他寫下那句名言的核心:GOTO「把程式的執行過程變成一隻游標在頁面上亂竄,這種 unbridled use(不受節制地使用)將是災難」。
線索二:Böhm-Jacopini 定理——三種結構即可表達一切#
如果 GOTO 要被取消,拿什麼取代?答案是 1966 年義大利學者 Corrado Böhm 與 Giuseppe Jacopini 證明的結構化定理:任何單入口單出口(single-entry single-exit)的流程圖,都可以只用三種控制結構組合表達:
- 順序(sequence): \(S_1;\ S_2;\ \dots\ S_n\) ——一個接一個執行。
- 選擇(selection):
if P then S1 else S2——二元分支。 - 循環(iteration):
while P do S——條件重複。
用數學語言說:
這個定理的深意在於:它證明了 GOTO 在表達能力上是多餘的——取消 GOTO 不會喪失任何計算能力。Dijkstra 在信中引用了這個結果,把「GOTO 可否被取代」從哲學辯論變成數學定理。而結構化程式的關鍵美德是:三種結構都是單入口單出口,因此可以層層巢狀組合,程式成為一棵從上到下、可逐塊理解與驗證的樹。
線索三:從「證明正確」到「設計正確」#
Dijkstra 的推理還有一層:他相信程式應該能被數學證明正確。GOTO 滿天飛的程式,其正確性證明需要無窮多個跳躍案例;而結構化程式(如 while 迴圈)可以用循環不變式(loop invariant)歸納證明。這個推理催生了他後來的名著《A Discipline of Programming》(1976)與最弱前置條件(weakest precondition)理論。程式設計從「寫了再 debug」轉變為「設計時就證明正確」——這是形式化方法的濫觴。
結案報告#
這封信引爆了結構化程式設計運動,其影響延續至今:
- 語言層面:1970 年代的語言紛紛捨棄或限制 GOTO——1970-Pascal.md 的 Pascal 幾乎不需要 GOTO;C 語言以
while/for/if-else為主幹;Ada、Modula-2 全面結構化。連 BASIC 也在 1980 年代進化出無行號的 QBASIC 與結構化的 Visual Basic。 - 方法論層面:Niklaus Wirth 的「逐步求精(stepwise refinement)」、David Parnas 的模組化資訊隱藏、乃至頂向下設計,都建立在結構化定理之上。軟體工程從此成為一門學科,NATO 的加米施會議正是其開端。
- 教育層面:結構化程式設計成為 1970–80 年代大學入門課程的教條,「禁止使用 GOTO」寫進了無數課堂規則與公司程式碼規範。
- 理論層面:Dijkstra 的論證風格催生了 Hoare 邏輯(1969,Hoare 為 Pascal 的前身 ALGOL W 建立的公理語義)、最弱前置條件、以及今日的形式化驗證工具(SPIN、TLA+)。
- 餘音:GOTO 並未完全消失——C 的
goto仍存在(用於錯誤處理跳出多層迴圈)、Linux 核心程式碼裡仍有節制地使用。Donald Knuth 1974 年的〈Structured Programming with go to Statements〉提出和解:GOTO 有其低階用途,但必須節制且跳躍方向向下。這場辯論至今偶爾重演,但主流共識已定:控制流應由結構而非跳躍組成。
一句話總結:這是程式設計史上最著名的「一頁紙偵查報告」——它用一封信、一個定理(Böhm-Jacopini),把 GOTO 釘上了歷史的恥辱柱,換來了結構化程式設計與現代軟體工程。
證據與工具#
以下用 Python 對照「GOTO 滿天飛」與「結構化」兩種寫法,並模擬 Böhm-Jacopini 定理:
# 案發現場:GOTO 版本(非結構化,模擬 BASIC 的行號跳躍)
program = {
100: "IF X > 0 THEN GOTO 300",
200: "LET Y = Y + 1",
210: "GOTO 500",
300: "LET Y = Y - 1",
310: "IF Y < 0 THEN GOTO 200",
400: "PRINT Y",
410: "GOTO 100",
}
def goto_flow(prog, start=100):
"""模擬 GOTO 程式的控制流:靜態文本 vs 動態跳躍順序完全脫節"""
visited, pc, trace = [], start, []
while pc not in visited and pc in prog:
visited.append(pc)
trace.append(pc)
stmt = prog[pc]
if "GOTO" in stmt:
pc = int(stmt.split("GOTO ")[1]) # 跳到任意行號
else:
keys = sorted(prog.keys())
pc = keys[(keys.index(pc) + 1) % len(keys)] if keys.index(pc) + 1 < len(keys) else start
return trace
print("GOTO 版動態執行軌跡(與文本順序脫節):", goto_flow(program))
# 結案報告:結構化版本(while/if-else,從上到下單一入口出口)
def structured_flow(x):
y = 0
while x > 0: # 循環:條件重複
y = y - 1
if y < 0: # 選擇:二元分支
y = y + 1 # 順序:語句接續
x = x - 1
return y
print("結構化版執行結果:", structured_flow(5))
# Böhm-Jacopini 定理模擬:任意流程圖可用三種結構還原
def flowchart_to_structured(nodes, edges, entry):
"""把流程圖(節點+邊)轉換為結構化程式的骨架:
順序(鏈)、選擇(分岔)、循環(回邊)"""
result = []
visited = set()
def emit(n):
if n in visited or n is None: return
visited.add(n)
node = nodes.get(n)
outs = edges.get(n, [])
if node is not None:
result.append(("SEQ", node)) # 順序結構
if len(outs) > 1:
result.append(("IF-ELSE", outs)) # 選擇結構
for o in outs: emit(o)
elif outs and outs[0] < n:
result.append(("WHILE-BACK", outs[0])) # 循環結構(回邊)
elif outs:
emit(outs[0])
emit(entry)
return result
nodes = {1: "x > 0 ?", 2: "y = y - 1", 3: "y < 0 ?", 4: "x = x - 1", 5: "return y"}
edges = {1: [2, 5], 2: [3], 3: [4, 2], 4: [1], 5: []}
print("\n流程圖還原為三種結構:")
for kind, detail in flowchart_to_structured(nodes, edges, 1):
print(f" {kind}: {detail}")
print("\n結論:順序 + 選擇 + 循環,即可表達一切演算法——GOTO 在表達能力上是多餘的")
執行結果顯示三件事:第一,GOTO 版的動態軌跡(如 100→300→310→200→…)與文本幾何順序完全脫節——這正是 Dijkstra 指出的認知災難;第二,結構化版本只用 while 與 if-else 就表達了相同邏輯,從上到下單一入口出口;第三,流程圖還原器把任意的節點與邊分解為 SEQ、IF-ELSE、WHILE-BACK 三種結構——這就是 Böhm-Jacopini 定理的具體呈現:順序、選擇、循環即可表達一切演算法。