结构化编程
C语言就是一种典型的结构化编程语言。之所谓结构化,是相对于非结构化编程而言的。
非结构化的时代
结构化编程中的顺序结构就是代码按照编写的顺序执行,选择结构就是if/else,而循环结构就是do/while。但是在使用汇编写代码时,面对的都是各种寄存器和内存地址。那些在高级语言中经常面对的变量,需要自己想办法解决,而类型,则统统没有。至于前面提及的那些控制结构,除了顺序结构之外,在汇编层面也是不存在的。但是在汇编里有个goto,它可以让代码跳转到另外一个地方继续执行。还有几个比较指令,让你可以比较两个值。
比如,if语句,就是执行一个表达式,根据表示式返回的真假,决定执行if后的代码还是else后的代码。如果是汇编,就是先执行一段代码,把执行结果和0比较,如果不等于0就接着执行,等于0就跳转到另外一个地方执行。也就是if的执行逻辑。
下图为java代码和java字节码的对比。
相对的理解do/while也就类似了,就是在判断之后,是决定跳到另外一个地方,还是继续执行下面的代码。如果执行下面的代码,执行到后面就会有一个goto让我们跳回来,再作一次判断。
在汇编时代,程序员面对的的确是这些汇编指令,他们站在直接使用指令的角度去思考。所以,他们更习惯按照自己的逻辑去写,这其中最方便的写法当然就是需要用到哪块逻辑,就goto到哪里执行一段代码,然后,再goto到另外一个地方。这种写起来自由自在的方式,在维护起来却会遇到极大的挑战,因为你很难预测代码的执行结果。有人可能只是图个方便,就goto到一个地方继续执行。可只要代码规模稍微一大,就几乎难以维护了,这便是非结构化的编程方式。
于是,有个人站出来,发表了一篇叫做 《GoTo是有害的》的文章,提出编程要有结构,结构化编程的概念应运而生。有了新的更高级却也更简单的模型,入门门槛就大幅度降低了,更多的人就可以加入进来,进一步促进这门语言的发展。使用的人越来越多,就有了更强的驱动力去优化底层的实现,时至今日,已经很少有人敢说自己手写的汇编性能一定优于编译器优化后的结果。
功能分解
功能分解就是将模块按照功能进行拆分。这样一来,一个大问题就会被拆解成一系列高级函数的组合,而这些高级函数各自再进一步拆分,拆分成一系列的低一级的函数,如此一步步拆分下去,每一个函数都需要按照结构化编程的方式进行开发。
采用结构化编程,一方面限制了goto的使用,对程序控制权的直接转移施加了约束。另一个方面就是功能分解:将大问题拆分成小问题,逐步递归下去,拆分成更小的、可证明的单元。但是goto语句的存在就会影响了问题的递归拆分,导致问题无法被拆分。
这一思想符合人们解决问题的直觉,对软件开发产生了深远的印象。以此为基础,后来出现各种结构化分析和结构化设计的方法。将大型系统拆分成模块和组件,这些模块和组件再做进一步的拆分,这些都是来自结构化编程的设计思想。
不足
虽然,结构化编程是比汇编更高层次的抽象,程序员们有了更强大的工具,但人们从来不会就此满足,随之而来的是,程序规模越来越大。这时,结构化编程就显得力不从心了。用一个设计上的说法形容结构编程就是“抽象级别不够高”。
这就好比你拿着一个显微镜去观察,如果你观察的目标是细菌,它能够很好地完成工作,但如果用它观察一个人,你恐怕就很难去掌握全貌了。结构化编程是为了封装低层的指令而生的,而随着程序规模的膨胀,它组织程序的方式就显得很僵硬,因为它是自上而下进行分解的。
一旦需求变动,经常是牵一发而动全身,关联的模块由于依赖关系的存在都需要变动,无法有效隔离变化。显然,如何有效地组织这么大规模的程序并不是它的强项,所以,结构化编程注定要成为其它编程范式的基石。
如果站在今天的角度看,结构化编程还存在一个问题,就是可测试性不够,道理和上面是一样的,它的依赖关系太强,很难拆出来单独测试一个模块。
所以,仅仅会结构化编程,并不足以让我们做出好的设计,必须把它与其他编程范式结合起来,才能应对已经日益膨胀的软件规模。
