1968 - 結構化程式設計(GOTO 有害論)#
案件摘要#
1968 年 3 月,Edsger W. Dijkstra(1930–2002) 在《ACM 通訊》 發表了一封只有一頁半的信件:
「GOTO 語句被認為有害」(Go To Statement Considered Harmful)。
這封信引發了程式設計史上最激烈的辯論—— 「結構化程式設計革命」:
1972 年,Dijkstra 獲得圖靈獎——這封信是主要原因之一。 對角線證明(Böhm–Jacopini 定理,1966)證明:任何程式都可以不用 GOTO 寫出—— GOTO 是不必要的!
前因 -- 為什麼會有這個案子#
- GOTO 的混亂(1950s–1960s): 組合語言的跳躍(jump)被帶進高階語言(FORTRAN 的
GOTO 10)—— 程式的控制流可以跳到任何地方:「義大利麵條式程式碼」(spaghetti code)——控制流像一團麵條。10 IF (X) 20, 30, 40 20 GOTO 50 30 GOTO 10 40 X = X + 1 GOTO 10 50 ...
- 軟體危機(1965–1970):
- IBM OS/360(1964–1969):5000 人年、5 億美元的專案—— 延期、超支、千瘡百孔(Brooks 的《人月神話》1975,1999 圖靈獎);
- 1968 年 NATO 會議:「軟體工程」這個詞的誕生—— 軟體的問題被正式承認為「工程問題」。
- Böhm–Jacopini 定理(1966): Corrado Böhm 與 Giuseppe Jacopini 證明:
這是理論的依據——GOTO 可以被消除! 但理論證明 ≠ 實務可行——需要一個「旗手」。 - Dijkstra 的信(1968): Dijkstra 寫信給《ACM 通訊》的編輯,點燃了革命:
「優秀的程式設計師的品質,與他們能想出的『聰明』資料結構成正比, 而與他們能塞進程式的『聰明』程式碼成反比。」 Dijkstra 的洞察:GOTO 破壞了「程式的靜態結構 = 動態執行」的對應—— 看程式碼時無法知道執行的順序。
線索與推理 -- 數學式、程式、理論#
第一條線索:三種結構(Böhm–Jacopini 定理)#
任何程式都可以只用三種結構: | 結構 | 語法 | 語義 | |------|------|------| | 順序 | A; B; C | 依序執行 | | 選擇 | if (p) A else B | 二路分支 | | 迴圈 | while (p) A | 條件重複 | Böhm–Jacopini 定理的證明(結構化程式設計的理論依據): 任何流程圖的節點都可以被「摺疊」成這三種結構的組合。
第二條線索:單一入口、單一出口(SESE)#
結構化程式設計的核心原則:
這保證了「推理的可組合性」: - 若結構 A 正確、結構 B 正確,則 A; B 正確—— - GOTO 破壞了這個性質(跳躍可以從結構的中間離開)。 Hoare 邏輯(1969,1980 圖靈獎):
結構化的推理規則:
只有 SESE 的結構才能這樣推理——GOTO 的跳躍使推理不可能。
第三條線索:複雜度的對比(圈複雜度)#
圈複雜度(cyclomatic complexity,McCabe, 1976):
- 結構化的程式: \(V(G)\) = 判斷點的個數 + 1(可控);
- GOTO 的程式: \(V(G)\) 爆炸(不可控)。 實務準則: \(V(G) > 10\) 的函數需要重構——這是現代靜態分析的基礎。
Python:GOTO 與結構化的對比(圈複雜度)#
import re
# 1) 圈複雜度的計算(McCabe, 1976)
def cyclomatic_complexity(code):
"""V(G) = E - N + 2P ≈ 判斷點數 + 1"""
# 簡化:判斷點 = if/while/for/and/or 的數量
decisions = len(re.findall(r'\bif\b|\bwhile\b|\bfor\b|\band\b|\bor\b|case|goto', code))
return decisions + 1
# GOTO 風格(FORTRAN 66 的義大利麵條)
goto_style = """
10 IF (X .GT. 0) GOTO 20
X = X + 1
GOTO 10
20 IF (Y .GT. 0) GOTO 30
Y = Y + 1
GOTO 10
30 STOP
"""
# 結構化風格(while + if)
structured_style = """
while x <= 0:
x = x + 1
while y <= 0:
y = y + 1
return
"""
print("圈複雜度對比(McCabe, 1976):")
print(f" GOTO 風格:V(G) = {cyclomatic_complexity(goto_style)}")
print(f" 結構化風格:V(G) = {cyclomatic_complexity(structured_style)}")
print(" → 結構化降低複雜度(可推理、可測試)\n")
# 2) Böhm–Jacopini 定理:GOTO 可以被消除
print("Böhm–Jacopini 定理(1966):")
print(" 任何流程圖都可以只用三種結構表達:")
print(" 順序(A; B; C)")
print(" 選擇(if p A else B)")
print(" 迴圈(while p A)")
print(" → GOTO 是不必要的(理論依據)\n")
# 3) 結構化程式設計的實踐(Python 沒有 GOTO)
def process_items(items):
"""結構化的處理(順序 + 選擇 + 迴圈)"""
result = []
for item in items: # 迴圈
if item > 0: # 選擇
result.append(item * 2)
elif item == 0:
result.append(0)
else:
result.append(-1)
return result # 單一出口
# GOTO 的等價寫法(用例外模擬跳躍——Python 不鼓勵)
class GotoException(Exception):
pass
def process_items_goto(items):
"""GOTO 風格(用例外模擬)——不推薦"""
result = []
try:
i = 0
while True:
if i >= len(items):
raise GotoException("end")
item = items[i]
if item > 0:
result.append(item * 2)
i += 1
except GotoException:
pass
return result
items = [1, -2, 0, 3]
print("結構化 vs GOTO:")
print(f" 結構化:{process_items(items)}")
print(f" GOTO: {process_items_goto(items)}")
print(" → 結果相同,但結構化可讀、可推理、可維護\n")
# 4) Hoare 邏輯的推理(前置/後置條件)
print("Hoare 邏輯(1969):")
print(" {P} S {Q}:前置條件 P、程式 S、後置條件 Q")
print(" 例:{n ≥ 0} factorial(n) {result = n!}")
print(" 推理規則(順序組合):")
print(" {P} A {R}, {R} B {Q}")
print(" ------------------")
print(" {P} A;B {Q}")
print(" → 結構化(SESE)才能組合推理;GOTO 使推理不可能")
圈複雜度對比(McCabe, 1976):
GOTO 風格:V(G) = 5
結構化風格:V(G) = 3
→ 結構化降低複雜度(可推理、可測試)
Böhm–Jacopini 定理(1966):
任何流程圖都可以只用三種結構表達:
順序(A; B; C)
選擇(if p A else B)
迴圈(while p A)
→ GOTO 是不必要的(理論依據)
結構化 vs GOTO:
結構化:[2, -1, 0, 6]
GOTO: [2, -1, 0, 6]
→ 結果相同,但結構化可讀、可推理、可維護
Hoare 邏輯(1969):
{P} S {Q}:前置條件 P、程式 S、後置條件 Q
例:{n ≥ 0} factorial(n) {result = n!}
推理規則(順序組合):
{P} A {R}, {R} B {Q}
------------------
{P} A;B {Q}
→ 結構化(SESE)才能組合推理;GOTO 使推理不可能
結案 -- 後果與影響#
- 程式設計的革命(1968–):
- Pascal(1970,Wirth,1984 圖靈獎):為教學而設計的結構化語言—— Wirth 明確說 Pascal 是「GOTO 有害論」的實踐;
- C(1972,Ritchie,1983 圖靈獎):保留 GOTO 但鼓勵結構化;
- Ada(1983)、Java(1995):無 GOTO 或極少使用。
- 軟體工程的誕生(1968):
- 模組化:把程式分成模組(單一職責);
- 抽象化:介面與實作分離;
- 測試與驗證:Hoare 邏輯、不變量。
- Hoare 邏輯的形式驗證(1969–):
- SeL4(2009):第一個被完整形式驗證的作業系統核心;
- CompCert(2006,Leroy):被驗證的 C 編譯器;
- AWS(2020s):Amazon 用形式方法驗證分佈式系統(TLA+,Lamport 2013 圖靈獎)。
- 靜態分析的基礎(1976–):
現代的程式碼品質工具全部基於 Dijkstra 的思想—— 「程式的結構可以被度量」。 - Dijkstra 的後續(1968–2002): - THE 作業系統(1968):分層設計(THE multiprogramming system); - 信號量(1965):並行的同步; - 《程式設計的修養》(A Discipline of Programming, 1976)—— 最弱前置條件(weakest precondition)的理論; - Dijkstra 2002 年去世——他的手記(EWD notes,1300+ 篇)是程式設計的遺產。 - 哲學的影響:
Dijkstra 的名言:
「簡單是可靠的先決條件。」(Simplicity is prerequisite for reliability.) 「程式設計師的工作不是讓程式能跑,而是讓程式明顯地正確。」 這是軟體工程的哲學基礎—— KISS 原則、YAGNI、最小驚訝原則都是 Dijkstra 思想的後代。 - 歷史定位:Dijkstra 的一頁半的信改變了整個行業——
「一頁半的信,五十年的影響」。
關鍵人物與文獻#
- E. W. Dijkstra:Go To Statement Considered Harmful(1968);A Discipline of Programming(1976);信號量(1965);1972 圖靈獎。
- C. Böhm & G. Jacopini:Böhm–Jacopini 定理(1966)——結構化的理論依據。
- C. A. R. Hoare:Hoare 邏輯(1969);CSP(1978);1980 圖靈獎。
- N. Wirth:Pascal(1970);1984 圖靈獎——結構化語言的實踐。
- F. P. Brooks:The Mythical Man-Month(1975);OS/360;1999 圖靈獎。
- 相關案件:
1959-迪傑斯特拉最短路徑.md、1968-克努特演算法藝術.md、1973-UNIX作業系統.md、2006-艾倫編譯器最佳化.md。