調整控制流程
三個調整控制流程的方式為
運行一個子程式,然後運行下一個(順序)
依照布爾變數的結果,決定運行二段子程式中的一段(選擇)
重複運行某子程式,直到特定布爾變數為真為止(循環)
1.運行一個子程式,然後運行下一個(順序)
2.依照布爾變數的結果,決定運行二段子程式中的一段(選擇)
3.重複運行某子程式,直到特定布爾變數為真為止(循環)
匹配上述條件的結構圖需要額外的比特變數(在原始證明中放在額外的整數變數中),以紀錄原來程式運行到的位置,此種建構法是以伯姆的程式語言P′′為基礎。
起源及變體
一般認為此理論最早是在1966年科拉多·伯姆及朱塞佩·賈可皮尼(Giuseppe Jacopini)的論文中提出 。大衛·哈雷爾在1980年曾提到這篇論文廣受認可,尤其在結構化程式理論的支持者中。哈雷爾也提到“由於其論文比較技術的風格,因此較常被引用,較少人真正詳讀過內容。”,在看了1980年以前的大量論文後,哈雷爾認為結構化程式理論被錯誤詮釋為一個結果較簡單的大眾定理(folk theorem),而此結果可以追溯到馮·諾依曼及史蒂芬·科爾·克萊尼現代計算理論的論文。
哈雷爾也提到較通用的“結構化程式理論”名稱是在1970年代初由哈倫·米爾斯提出。
單一while循環的大眾定理版本
此版本的定理將原來定理中的程控流程改為一個while循環,模擬在原來非結構化的程式中,程式計數器走過所有可能標記(流程圖方塊)的情形。哈雷爾將此版大眾定理的源頭追溯到兩篇論文,一篇是1946年描述馮·諾伊曼結構,用單一while循環說明程式計數器的運作原理,哈雷爾也注意到大眾定理中用到的單一循環基本上可以提供馮·諾伊曼式電腦運行流程的操作語義。。另一篇更早期的論文則是史蒂芬·科爾·克萊尼1936年的正規形式定理(Kleene's T predicate)論文。
高德納批評這種轉換後的結果類似以下的偽代碼,重點是在此轉換中完全破壞了原程式的結構。Bruce Ian Mills也有類似的看法:“塊狀結構的精神是其風格,不是使用的語言。利用模擬馮·諾伊曼結構的方式,可以將任何一個麵條式代碼轉換為塊狀結構的語言,但它麵條式代碼的本質沒有改變。”
伯姆及賈可皮尼的證明
伯姆及賈可皮尼的證明是以流桯圖的結構歸納法為基礎,由於用到圖模式匹配,其證明在實務上不能當作是程式轉換算法,因此開創了此一領域的研究。
相關的討論及研究
因為伯姆及賈可皮尼建構的方式過於複雜,因此此證明沒有回答結構化編程是否適用於軟體開發的問題,而是引發了後續相關的討論及爭議。在兩年之後的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定義的化約是指用相同的“基本動作”及判斷,計算相同的函式,但是可能用不同的控制流程(此處的化約比伯姆及賈可皮尼定理中提及的範圍要窄)。受到這個結論的啟發,Thomas J. McCabe在他引入循環複雜度的論文中的第四部分,描述了對應非結構化程式控制流圖(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,並開始循環。
1.找出程式中的基礎方塊。
2.將每一個方塊的起始點指定不重複的編號,將每個方塊的結束點用所連線方塊起始點的編號來標示,程式結束點編號指定為0,程式起始點編號指定為1。
3.將程式分區為基礎方塊。
4.若某方塊的起始點只對應一個方塊的結束點,將二個方塊合併。
5.定義程式中的一個新的變數,假設為L。
6.針對其他沒有合併的結束點,增加一行指令,將L設定為該結束點的編號。
7.將所有基礎方塊合併成一個選擇執行指令,依L的數值運行對應的程式。
8.創建一個循環,若L不為0,繼續運行循環。
9.創建程式,一開始將L設為1,並開始循環。
註:將一些選擇分支轉變為子程式可以改進所得結果。