结构化编程

(重定向自結構化編程

結構化程式設計(英語:Structured programming),一種編程典範。它採用子程序块结构for迴圈以及while迴圈等結構,來取代傳統的 goto。希望藉此來改善计算机程序的明晰性、品質以及開發時間,並且避免寫出麵條式代碼

結構化程式設計在1960年代開始發展,科拉多·伯姆英语Corrado Böhm朱塞佩·賈可皮尼(Giuseppe Jacopini)於1966年5月在《Communications of the ACM》期刊發表論文[1],說明任何一個有goto指令的程式,可以改為完全不使用goto指令的程式,後來艾兹赫尔·戴克斯特拉在1968年也提出著名的論文《GOTO陳述有害論》(Go To Statement Considered Harmful)[2],因此結構化程式設計開始盛行,此概念理論上可以由結構化程式理論所證明,而在實務上,當時也有像ALGOL一樣,有豐富控制結構的程式語言來實現結構化程式設計。

底層的結構化程式設計

結構化的程式是以一些簡單、有層次的程式流程架構所組成,可分為循序(sequence)、選擇(selection)及重複(repetition)。

  • 循序是指程式正常的執行方式,執行完一個指令後,執行後面的指令。
  • 選擇是依程式的狀態,選擇數段程式中的一個來執行,一般會使用if..then..else..endifswitchcase等關係字來識別。
  • 重複是指一直執行某一段程式,直到滿足特定條件,或是一集合體中的所有元素均已處理過,一般會使用whilerepeatfordo..untildo...while等關鍵字識別。一般會建議每個迴圈只能有一個進入點(戴克斯特拉的結構化程式設計要求每個迴圈只能有一個進入點及一個結束點,有些程式語言仍有此規定)。

若一個程式語言的語法允許用成對的關鍵字包圍一段程式,形成一個結構,這種程式語言稱為有「塊結構」,這類的結構包括用ALGOL 68if..fi包圍的程式,或是在PL/I中用BEGIN..END包圍的一段程式,或是在C語言中用大括號{...}包圍的一段程式。

結構化程式語言

用任何語言都可以進行結構化程式設計,不過一般較常使用过程式程式語言。早期的結構化程式語言包括ALGOLPascalPL/IAda,不過後來大部份的程序式程式語言都鼓勵使用結構化程式設計,有時也會特意的省去一些特性(例如不支援goto指令)使得非結構化的程式設計更加困難。

歷史

理論基礎

結構化程式理論可做為结构化编程的理論基礎,結構化程式理論中提到利用循序、選擇及重複這三種組合程式的方式,可以表示所有可计算函数。上述的三種結構已足以表示CPU中的指令周期,也可以表示圖靈機的運作,以此觀點來看,處理器所執行的指令可視為是某種「結構化程式」,雖然整個程式可能不是一個結構化程式。一般都認為結構化程式理論是歸功於伯姆和賈可皮尼於1966年發表的論文,其中一個原因可能是戴克斯特拉引用過此論文。結構化程式理論未提及如何撰寫結構化程式,也沒有提到結構化程式的分析,後來1960至1970年代時,戴克斯特拉、羅伯特·弗洛伊德東尼·霍爾等計算機科學家在此領域有許多的貢獻。

爭議

结构化编程中一項重要的原則是減少甚至禁止goto指令的使用,不過不是所有計算機科學家都贊成禁止使用goto指令。高德納贊成程式設計時需考慮可讀性,但他不贊成禁用goto指令。在其1974年發表的論文《使用goto指令的結構化編程》(Structured Programming with Goto Statements)中,他提出了一些程式,使用goto指令可以使得程式更清楚而有效率,也不會犧牲程式的可讀性。高德納提出了一個較鬆的結構限制要求:將程式以流程圖表示,前進的分支在流程圖的左側,倒退的分支在流程圖的右側,所有分支均不得交叉。

结构化编程在1970年有很大的進展,IBM的研究員哈倫·米爾斯英语Harlan Mills將结构化编程應用在紐約時報研究檔案索引系統的開發,此計劃相當成功,因此許多公司開始使用结构化编程,不過戴克斯特拉評論米爾斯使用的方式和一些已發表論文中的方式不同。

到1987年時在計算機科學領域仍有針對结构化编程的爭論,弗蘭克·魯賓發表了一篇論文《「goto有害論」是有害的》("GOTO considered harmful" considered harmful),引發許多的反對,戴克斯特拉本人也批評魯賓及其追隨者的論點。

影響

在二十世紀末時絕大多數的計算機科學學者均已同意使用結構化程式設計的好處,原來缺乏程式結構的高階程式語言(如FORTRANCOBOLBASIC)也都已加入此特性。

例外情形

异常处理

子程式很少會有一個以上的進入點,相對的,有時子程式會有一個以上的結束點,表示剩下的程式不需執行,或因為一些原因,造成無法執行後續的程式。

以下是是一個由檔案中讀取資料並處理的程式範例:

open file;
 while (reading not finished) {
   read some data;
   if (error) {
     stop the subprogram and inform rest of the program about the error;
   }
 }
 process read data;
 finish the subprogram;

其中「stop and inform」的步驟可以利用多種方式達成,包括產生一個異常(exception)、利用return指令回到上一層的程式、使用配合標記的break指令,或是使用goto。當子程式有二個結束點時,就違背了戴克斯特拉的结构化编程原則。但此情形下若強制要撰寫只有一個結束點的子程式又相當麻煩,而且若有幾個不同的錯誤處理,錯誤產生後有不同的清除方式,單一結束點的程式會相當難以閱讀及理解,甚至比未結構化使用goto的程式相當。

許多程式語言就提供了在结构化编程中產生多個結束點的方式。C語言允許使用continuebreakreturn指令來產生結構的多個結束點,C++還可以用throw產生異常,在結構外再用catch進行異常的處理,有些語言則有配合標記的break指令(類似一般的break指令,但可以跳出不只一層的結構)。

狀態機

有些程式(例如語法分析器或是處理通訊協定的程式)有許多的狀態英语state (computer science),因此程式進行的過程會在各狀態中切換,此架構不容易簡化成基本的控制結構。可以將此架構各狀態下的程式分別獨立為子程序,再用一個變數表示目前的狀態,(可參考trampoline英语trampoline (computers)),另一種作法是用goto的方式切換到新狀態對應的程式。

函數退出

Linux Kernel的代碼風格指南,在退出函數的某些情況會使用goto,有以下四點理由:

  1. 減少條件陳述使程式碼更容易了解
  2. 防止開發人員疏忽,沒有更新所有退出點產生的錯誤
  3. 減少巢狀結構
  4. 降低編譯器優化冗余程式碼所需的工作

相關條目

参考文献

  1. ^ Böhm, Jacopini. "Flow diagrams, turing machines and languages with only two formation rules" Comm. ACM, 9(5):366-371, May 1966.
  2. ^ Edsger Dijkstra. Go To Statement Considered Harmful. Communications of the ACM (PDF). March 1968, 11 (3): 147–148. doi:10.1145/362929.362947. The unbridled use of the go to statement has as an immediate consequence that it becomes terribly hard to find a meaningful set of coordinates in which to describe the process progress. ... The go to statement as it stands is just too primitive, it is too much an invitation to make a mess of one's program.