結構化程式理論

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

  1. 執行一個子程式,然後執行下一個(順序)
  2. 依照布爾變數的結果,決定執行二段子程式中的一段(選擇)
  3. 重覆執行某子程式,直到特定布爾變數為真為止(循環)
3個調整控制流程的方式
3個調整控制流程的方式

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

起源及變體

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

哈雷爾也提到較通用的「結構化程式理論」名稱是在1970年代初由哈倫·米爾斯英語Harlan Mills提出[3]:381

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

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

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

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.

伯姆及賈可皮尼的證明

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

相關的討論及研究

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

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

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

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

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

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

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

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

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

在Cobol上的應用

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

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

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

相關條目

參考資料

  1. ^ 1.0 1.1 1.2 Dexter Kozen and Wei-Lung Dustin Tseng. The Böhm–Jacopini Theorem Is False, Propositionally (PDF). MPC 2008. [2015-05-21]. doi:10.1007/978-3-540-70594-9_11. (原始內容存檔 (PDF)於2021-04-27). 
  2. ^ CSE 111, Fall 2004, BOEHM-JACOPINI THEOREM. Cse.buffalo.edu. 2004-11-22 [2013-08-24]. (原始內容存檔於2020-02-07). 
  3. ^ 3.0 3.1 3.2 3.3 3.4 3.5 3.6 3.7 Harel, David. On Folk Theorems (PDF). Communications of the ACM. 1980, 23 (7): 379–389 [2015-05-21]. doi:10.1145/358886.358892. (原始內容存檔 (PDF)於2020-11-27). 
  4. ^ Bohm, Corrado; Giuseppe Jacopini. Flow Diagrams, Turing Machines and Languages with Only Two Formation Rules. Communications of the ACM. May 1966, 9 (5): 366–371. doi:10.1145/355592.365646. 
  5. ^ Donald Knuth. Structured Programming with go to Statements. Computing Surveys. 1974, 6 (4): 261–301. doi:10.1145/356635.356640. 
  6. ^ Bruce Ian Mills. Theoretical Introduction to Programming. Springer. 2005: 279. ISBN 978-1-84628-263-8. 
  7. ^ 7.0 7.1 Z. Ammarguellat. A control-flow normalization algorithm and its complexity. IEEE Transactions on Software Engineering. March 1992, 18 (3): 237–251 [2018-04-02]. ISSN 0098-5589. doi:10.1109/32.126773. (原始內容存檔於2019-04-06). 
  8. ^ Dijkstra, Edsger. Go To Statement Considered Harmful. Communications of the ACM. 1968, 11 (3): 147–148. doi:10.1145/362929.362947. (原始內容存檔於2007-07-03). 
  9. ^ 9.0 9.1 Roberts, E. [1995] 「Loop Exits and Structured Programming: Reopening the Debate頁面存檔備份,存於網際網路檔案館),」 ACM SIGCSE Bulletin, (27)1: 268–272.
  10. ^ E. N. Yourdon. Classics in Software Engineering. Yourdon Press. 1979: 49–50. ISBN 978-0-917072-14-7. 
  11. ^ Ashcroft, Edward; Zohar Manna. The translation of go to programs to 'while' programs. Proceedings of IFIP Congress. 1971.  The paper, which is difficult to obtain in the original conference proceedings due to their limited distribution, was republished in Yourdon's 1979 book pp. 51-65
  12. ^ David Anthony Watt; William Findlay. Programming language design concepts. John Wiley & Sons. 2004: 228. ISBN 978-0-470-85320-7. 
  13. ^ Kenneth C. Louden; Kenneth A. Lambert. Programming Languages: Principles and Practices 3. Cengage Learning. 2011: 422–423. ISBN 1-111-52941-8. 
  14. ^ KOSARAJU, S. RAO. "Analysis of structured programs," Proc. Fifth Annual ACM Syrup. Theory of Computing, (May 1973), 240-252; also in J. Computer and System Sciences, 9, 3 (December 1974), doi: 10.1016/S0022-0000(74)80043-7 cited by Donald Knuth. Structured Programming with go to Statements. Computing Surveys. 1974, 6 (4): 261–301. doi:10.1145/356635.356640. 
  15. ^ Ronald F. Brender. The BLISS programming language: a history. Software: Practice and Experience. 2002-08-01, 32 (10): 955–981 [2018-04-02]. ISSN 1097-024X. doi:10.1002/spe.470 (英語). 
  16. ^ The original paper is Thomas J. McCabe. A Complexity Measure. IEEE Transactions on Software Engineering. December 1976: 315–318.  For a secondary exposition see Paul C. Jorgensen. Software Testing: A Craftsman's Approach, Second Edition 2nd. CRC Press. 2002: 150–153 [2015-06-01]. ISBN 978-0-8493-0809-3. (原始內容存檔於2015-04-28). 
  17. ^ Lyle Ramshaw. Eliminating go to's while preserving program structure. Journal of the ACM (JACM). 1988-10-01, 35 (4): 893–920 [2018-04-02]. ISSN 0004-5411. doi:10.1145/48014.48021. 
  18. ^ Godfrey Nolan. Decompiling Java. Apress. 2004: 142. ISBN 978-1-4302-0739-9. 
  19. ^ 存档副本 (PDF). [2015-06-02]. (原始內容存檔 (PDF)於2016-03-04). 
  20. ^ 存档副本 (PDF). [2015-06-02]. (原始內容存檔 (PDF)於2016-03-03). 
  21. ^ 存档副本. [2015-06-04]. (原始內容存檔於2020-07-10).