结构化编程

结构化程式设计(英语: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.