結構化程式理論

<image>

結構化程式理論也稱為伯姆-賈可皮尼理論或Böhm-Jacopini理論，是一項程式語言研究的結果，說明只要一種程式語言可以依三個方式組合其子程式及調整控制流程，每個可計算函數都可以用此種程式語言來表示。三個調整控制流程的方式為

- 執行一個子程式，然後執行下一個（順序）
- 依照布爾變數的結果，決定執行二段子程式中的一段（選擇）
- 重覆執行某子程式，直到特定布爾變數為真為止（循環）

符合上述條件的結構圖需要額外的位元變數（在原始證明中放在額外的整數變數中），以紀錄原來程式執行到的位置，此種建構法是以伯姆的程式語言P′′為基礎。

## 起源及變體

一般認為此理論最早是在1966年科拉多·伯姆及朱塞佩·賈可皮尼（Giuseppe Jacopini）的論文中提出。大衛·哈雷爾在1980年曾提到這篇論文廣受認可，尤其在結構化程式理論的支持者中。哈雷爾也提到「由於其論文比較技術的風格，因此較常被引用，較少人真正詳讀過內容。」，在看了1980年以前的大量論文後，哈雷爾認為結構化程式理論被錯誤詮釋為一個結果較簡單的大眾定理（folk theorem），而此結果可以追溯到馮·諾依曼及史蒂芬·科爾·克萊尼現代計算理論的論文。

哈雷爾也提到較通用的「結構化程式理論」名稱是在1970年代初由哈倫·米爾斯提出。

### 單一while迴圈的大眾定理版本

此版本的定理將原來定理中的程式控制流程改為一個`while`迴圈，模擬在原來非結構化的程式中，程式計數器走過所有可能標記（流程圖方塊）的情形。哈雷爾將此版大眾定理的源頭追溯到兩篇論文，一篇是1946年描述馮·諾伊曼結構，用單一while迴圈說明程式計數器的運作原理，哈雷爾也注意到大眾定理中用到的單一迴圈基本上可以提供馮·諾伊曼式電腦執行流程的操作語義。另一篇更早期的論文則是史蒂芬·科爾·克萊尼1936年的正規形式定理（Kleene's T predicate）論文。

高德納批評這種轉換後的結果類似以下的偽代碼，重點是在此轉換中完全破壞了原程式的結構。Bruce Ian Mills也有類似的看法：「塊狀結構的精神是其風格，不是使用的語言。利用模擬馮·諾伊曼結構的方式，可以將任何一個麵條式代碼轉換為塊狀結構的語言，但它麵條式代碼的本質沒有改變。」

```Pascal
p := 1;
while p > 0 do begin
  if p = 1 then begin
    進行流程圖的步驟1;
    p := 流程圖的步驟1之後的步驟編號（若沒有後續步驟，數值為0）;
  end;
  if p = 2 then begin
    進行流程圖的步驟2;
    p := 流程圖的步驟2之後的步驟編號（若沒有後續步驟，數值為0）;
  end;
  ...
  if p = n then begin
    進行流程圖的步驟n;
    p := 流程圖的步驟n之後的步驟編號（若沒有後續步驟，數值為0）;
  end;
end.
```

### 伯姆及賈可皮尼的證明

伯姆及賈可皮尼的證明是以流桯圖的結構歸納法為基礎，由於用到圖模式匹配，其證明在實務上不能當作是程式轉換演演算法，因此開創了此一領域的研究。

## 相關的討論及研究

因為伯姆及賈可皮尼建構的方式過於複雜，因此此證明沒有回答結構化編程是否適用於軟體開發的問題，而是引發了後續相關的討論及爭議。在兩年之後的1968年，艾茲赫爾·戴克斯特拉就提出著名的「GOTO有害論」。

有些學者試圖使伯姆及賈可皮尼的研究結果更加純粹，因為其論文中沒有用到從迴圈中間跳出迴圈的`break`及`return`指令，因此學者認為這是不好的實作方式，學者們鼓勵每一個迴圈都只能有唯一的結束點，這種設計觀點整合到1968至1969年開發的Pascal中。從1969年到1990年代中期，學校常用Pascal來講授程式語言入門課程。

愛德華·尤登注意到1970年代時在有關是否用自動化方式改寫非結構化程式一事，有二元對立的觀點，反對者認為需要以結構化程式的方式去思考，而非一味改寫，而贊成者的論點是這類的修改實際上可以改善大部份已有的程式。最早提出自動化改寫程式概念的有1971年Edward Ashcroft及Zohar Manna的論文。

直接應用伯姆及賈可皮尼定理可能要引入額外的局部變量，也可能產生代碼重覆的問題，後者也稱為loop and a half problem。Pascal受到這些問題的影響，依照埃里克·S·羅伯茨的實驗研究，學習程式設計的學生難以用Pascal設計正確程式碼來解決簡單的問題，其中甚至包括從陣列中找尋一個元素的問題。一篇1980年由Henry Shapiro進行，而後被被羅伯茨引用的研究指出，若只用Pascal提出的流程控制指令，只有20%的人的解答是正確的，但若允許在迴圈中直接加入`return`的話，所有人都寫出了正確的答案。

S. Rao Kosaraju在1973年證明只要允許可以從任意深度迴圈中多層次跳出，就可以將程式轉換成結構化編程，而不用引入額外的變量。而且Kosaraju證明了存在一個嚴格的程式階層（現在稱為Kosaraju階層），針對任一整數n，存在一個程式，其中包括深度n的多層次跳出，而且在不引入額外變量的條件下，無法用深度小於n的跳出來實現。Kosaraju稱這種多層次跳出結構源於BLISS語言。BLISS語言中的多層次跳出形式為`leave ''label''`，實際上在BLISS-11版本中才引入到BLISS中，原始的BLISS只有單一層次的跳出。BLISS語言家族不提供無限制的跳轉指令，Java語言後來也引入類似BLISS語言中的多層次跳出指令。

Kosaraju的論文中有另一個較簡單的結論：若程式可以在不用額外變量（及多層次的跳出）下化約為結構化程式，其充份必要條件是程式中沒有一個迴圈有二個或二個以上的結束點。簡單來說，此處Kosaraju定義的化約是指用相同的「基本動作」及判斷，計算相同的函數，但是可能用不同的控制流程（此處的化約比伯姆及賈可皮尼定理中提及的範圍要窄）。受到這個結論的啟發，老托馬斯·J·麥凱布在他引入循環複雜度的論文中的第四部份，描述了對應非結構化程式控制流圖（CFG）的Kuratowski定理。使控制流圖變得無法結構化的最小子圖是：

- 從循環測試以外的地方跳出迴圈
- 直接跳躍到迴圈中
- 直接跳躍到一個判斷分支之中
- 直接跳出一個判斷分支

McCabe發現上述這些子圖不是彼此獨立的，程式無法結構化的充份必要條件是控制流圖中有子圖有上述四種條件中的三種（或三種以上）。McCabe也發現若非結構化的程式中包括其中四個條件中的一個，它一定還會包含另一個。這也是非結構化的程式流程會糾結到類似義大利麵的原因。McCabe也提供一個量化方式，說明一個程式和理想結構化程式之間的距離，並稱其為本質複雜度。

到1990年為止，學者們提出許多消除既有程式中跳轉指令，但又維持大部份控制架構的方式，也提出許多標示程式等價的方式，這些方式比簡單的圖靈等價要嚴格，以免造成類似上述大眾定理般的轉換結果。這些等價標示的嚴格程度指定了所需控制流結構的最小集合。1998年Lyle Ramshaw在ACM期刊的論文進行了相關的調查，也提出了自己的方法。Ramshaw的演演算法也用在Java反編譯器中，因為Java虛擬機有分支指令，以位移來表示分支跳轉的目標，但高級的Java語言只有多層次的`break`及`continue`指令。Ammarguellat在1992年提出一種轉換方式，回到強制單一結束點的作法。

## 在Cobol上的應用

1980年代IBM研究員哈倫·米爾斯管理COBOL構建設備（COBOL Structuring Facility）的開發時，將程式的結構化演演算法應用到COBOL語言中。米爾斯的轉換方式包括以下的步驟。

- 找出程序中的基礎方塊。
- 將每一個方塊的起始點指定不重覆的編號，將每個方塊的結束點用所連接方塊起始點的編號來標示，程式結束點編號指定為0，程式起始點編號指定為1。
- 將程序分割為基礎方塊。
- 若某方塊的起始點只對應一個方塊的結束點，將二個方塊合併。
- 定義程序中的一個新的變量，假設為L。
- 針對其他沒有合併的結束點，增加一行指令，將L設定為該結束點的編號。
- 將所有基礎方塊合併成一個選擇執行指令，依L的數值執行對應的程式。
- 建立一個迴圈，若L不為0，繼續執行迴圈。
- 建立程序，一開始將L設為1，並開始迴圈。

註：將一些選擇分支轉變為子程序可以改進所得結果。