反思计算机科学中的理论与工程
反思 理论 工程 计算机“理论联系实践简单来说就是理论不够实践来凑吗?” --by 唐三藏
提出问题
计算机学科发展至今,是时候做一些反思了!尽管有人还在怀疑,但是我已经不想再讨论“计算机科学是否科学”这样的问题,而想就计算机科学中的理论与工程进行若干思考。在过去的若干年,在各个不同的层面与场合,有识之士一直在强调“理论联系实践”。看得出,说这话的人很多,但真正能理解其中真实意味的太少。
什么是理论,什么是工程?避免陷入繁杂的考证,思考人类造房子的衍化的过程。不妨猜测,一个造房子的原始人是希望模拟出一个优雅的山洞,即对物理世界中客体的模拟。然后,在建造过程中发现了若干问题,比如:如何防止塌方、方便排水通风等,并不断总结经验得到某些原则,再进行不断改进。这些原则是否就是理论呢?也许是,也许不是。然而有点可以肯定,当“某个人造一所房子”发展为“某个地区的人造某种类型的房子”的时候,当这种原则可以适用于不同的地区与人民,可以流传并衍化,那么这种原则就接近于理论了,或者就成为了理论。要注意的是,理论并非僵化之物,它在变动。当人类可以造出摩天大楼的时候,当我们骄傲地声称人类掌握了建筑的理论的时候,建筑理论不会因为之前“原始人造房子”的理论肤浅而抹杀了其存在的价值,更不应该认为理论源自理论反作用于理论而脱离实践(工程)。
但是,理论必然需要脱离实践。所谓脱离,我指的是,理论必须是对实践的抽象,是对某些原则的推广。也就是说,抽象是理论与工程之间的区分标准。注意,如上所述,理论到工程并非一蹴而就,可能会经历多个时代的抽象与演进。并且,理论的演进的基础往往又是无数次对理论进行应用的工程实践。因此我们必须小心,当讨论某种理论的时候,我们必须留意自己所处的特定抽象高度、理论对工程的指导意义及工程实践对理论建立的挑战。否则,我们往往将陷入一种无的放矢。
实例说明理论与实践的关系
比如,很多小同学在忙着自己的理论,而老同学们看了就会嘲笑,“嘿,这也算理论”。更严肃地,很多专家会说,在数学家眼里,计算机科学这些理论都不是理论!请不要轻易地下如此结论,任何给出结论的人必须论证他的结论。我们可以承认计算机科学理论的幼稚,但是评判某种“理论”是否是理论只应该应用一种评判标准:这种理论是否对实践的高度抽象,是否可作用于一类问题(而不是一个问题),是否具有发展扩充的可能。以下我们举例说明刚才这些“理论”。
就程序设计而言,我们往往认为编程只是一项工程实践活动,是否真的如此呢?以下试图就此探讨,比如,我们要写一个Fibonacci数列的程序。无论是用C语言或者Lisp,最直观的方法就是写一个递归函数。如下程序(请大家用直觉来阅读这一段代码):
def fib_recursive(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib_recursive(n-1) + fib_recursive(n-2)
没有人会怀疑这是一项实践活动,然而这里存在这样的分歧。如果这是大学一年级的小同学写的程序,大家会觉得没问题;但是有可能一个大三的同学就会说(实际上,很多大一的同学也已经知道):噢,这个程序真是太不高效了,不用递归而用迭代法可以获得更好的效率。于是,高手大神们就写了如下程序:
def fib_iterative(n):
a, b = 0, 1
while n > 0:
a, b = b, a + b
n -= 1
return a
进一步,如果有好奇的大一同学要问,为什么迭代会高效?高效了多少?我们可以给出很工程的回答:你看,在求第10个Fibonacci数的时候,前一个程序用了10s而后一个程序只用了1s,很显然会快啊!为什么会这样?噢,你看递归程序重复计算了很多的中间值,对吧,只要你花一点点小脑的分析就可以知道了......然后小菜鸟就会惊呼,哇,师兄威武厉害大神!我们必须问,从一个工程的问题出发找到一个工程问题的解决方案,这里有从工程到理论吗?No!这里只是靠直觉,离理论尚远。
在学习了《算法导论 》一书之后,我们掌握了一些算法效率的表示方法,也许可以用Theta(2^n)或者Theta(n)来分别标注两个程序的效率,然后我们回答说,迭代算法的复杂性是Theta(n),是线性时间,很快。请注意,当走到这一步的时候,BigO、Theta等的概念并不是针对Fibonacci数列的,而是针对广泛的算法而言;我们也不仅仅是关注某种机器下运行多少秒,而是关注一种抽象的概念:线性时间或者指数时间等。于是,我们说,在工程实践中我们主动地应用了某种理论。
这种理论(复杂性理论)当然源自于长期对算法的分析与实践,作用于算法的分析与实践,又脱离了算法的分析与实践独立形成自己的知识体系。这使得在工程实践中,许多人根本没办法把理论与实践清晰地区分开来。谨慎地,我们还是要把握评判的准则:抽象。
再回到Fibonacci这个例子,如果有一个偏执的师弟坚持要问:是不是递归一定会比迭代要慢?请看,问题从Fibonacci出发但又脱离了原问题走向更宽泛的实例去。我相信,对于这个问题,不见得有多少人能准确地回答,这是一个非常好的问题。
为了更清晰,我们思考一下GCD算法(求两个整数的最大公因子)。我们可以递归也可以迭代,问题就变成:迭代是不是一定比递归要强?相信大部分的同学一定会说,无论如何,递归一定要消耗时间,尽管你递归的程序也是lg n次递归,但是必然要比迭代的lg n要慢。对此我暂且不反驳,我进一步问,是不是所有的递归程序都可以转换为迭代程序?如果有,难道就没有一个聪明的编译器可以把递归程序直接编译成迭代的程序吗?如果有这样的编译器,我可以非常直观地写递归程序,让编译器来处理效率的问题。
相信,走到这一步,大部分的同学对我都会有意见了:哪来这么智能的编译器啊?! 你自己直接写迭代程序不就好了?!鬼知道你这是什么鬼问题?!其实我是在问一个“理论”问题而不是问一个工程的问题,因为似乎这与工程无关了,不是吗?这个问题试图从具体的实例中脱离开来,成为一类问题。想象不出会有哪个工程师需要解决递归到迭代转换的问题。这就是理论开始的地方,但这离理论还有很长的距离!
联想到原始人在建造房子的事情,多年来他们肯定在造房子、住房子的循环中不断地发现问题提出问题解决问题,直至形成建筑科学,这是一个漫长的过程。关于程序设计的理论也是如此,只有一系列“鬼问题”被提出、被解决并形成通用的方法的时候,当递归的问题形成“递归论”的时候,当程序设计的组合技术成为“进程代数”的时候,程序设计才形成理论。当某种(或多种)理论成为某学科的中心与基础的时候,这门学科就成为了科学。 所以,要判断“计算机科学是否科学”就只需要考察一下计算机这门学科形成了多少种理论。
理论与实践之间的认知误区
要解答上述一系列问题只靠小脑肯定不行,要足够大的大脑经过长时间折腾才行,这并非本文的目的,在此我更多的是想反思,在我们计算机的教学中形成了什么样的误区。大部分同学奉命学C、C++和Java等,用一种语言为工具依葫芦画瓢写了很多(少)很多(少)的程序。努力编程的成了Coder,他们学习更多的热门的编程语言和技巧,直到这些工具能成为他养家糊口的主要手段。醒目的Coder们会很快就觉醒了,他们认为程序设计没有理论只有工程,直到多年以后,当面向大型程序束手无策的时候,依然没有认识到本应该在本科的时候得到更多理论与抽象的训练。不努力编程的同学诅咒大学只教了理论而不知什么是理论,认为大学只是浪费时间的地方而不知道是自己在浪费时间。后者是一种普遍的存在,但不是本文主要讨论的内容。状况很可悲,值得反思。
现在的大学非常强调实践,强调工程。为什么在大学里面就没有多少人敢于真诚地承认:课程需要理论呢?我们需要脱离(高于)实践的理论!如何评判一门课程有没有理论?抽象!在《程序设计》中我们有没有教抽象;在《算法设计》中我们有没有教抽象;在《操作系统》中我们有没有教抽象。在《计算机安全学》中我试图教但永远没人听的也是抽象。我认为,当我一直在灌输“安全的要务首先是定义”时,很多同学坐在课堂上想要冲上讲台揍我的感觉,因为我看到一片冷漠的眼神。
走在另一个反面,还有一部分先知先觉的同学,他们接触到了计算机理论的范畴,经过了多年的学习,他们成了计算机理论的最反动者!因为他们发现计算机理论真是太丑陋了,同时他们发现了数学之美,于是他们对计算机理论避之唯恐不及,他们论断:计算机科学不是科学,计算机理论不是真正的理论是工程的一部分而已!就算计算机科学是科学,它也仅仅是数学的一部分。诸如此类的论调颇为流行。持这种观点的往往是专家教授,所以流毒特别深。
果真如此“计算机科学不是科学”么?再次强调,理论、工程不可简单地在一个层面一个维度进行考察。必须坚持一种批判标准:抽象。理论不会因为它丑陋就会变成工程,工程不会因为漂亮就直接成为理论。简单地否定或者承认某种“理论”是或者不是理论毫无意义,反而会轻易地忽略了其中许多重要的内涵。 理性地运用自己的知识与能力去探索答案将更有价值。
2015年2月20日下午
2017年7月5日晚修订
2019年8月5日晨修改、添加小标题