个人简介 CAR.Hoare爵士,人们都亲切地称他Tony Hoare,是一位尊敬的英国计算机科学家。他最人让称道的莫过于在1960年,他26岁的时候开发出了快速排序。同时他还提出了Hoare逻辑,形式语言的通信顺序进程(CSP),并对Occam程序设计语言产生了重要影响。
QCon全球企业开发大会(QCon Enterprise Software Development Conference)是由C4Media媒体集团InfoQ网站主办的全球顶级技术盛会,每年在伦敦和旧金山、北京、东京召开。
1. 我是Sadek Drobi,我正在QCon采访Tony Hoare爵士。Tony Hoare爵士,你能简单地介绍下自己以及你在计算机科学方面长期以来的经历吗?
我的计算机科学从业经历应当开始于1960年我首次受雇于一家小的英国计算机产商,当时这家厂商相当成功。我开始是作为程序员加入的,不久之后我就成为了部门主管,掌管一个Algol 60编译器的项目,该编译器连同计算机一起出售。5年后我被提升为首席工程师,掌控整个软件业务。接着我很快又转到了研究室的的首席科学家这一职位,在此我投入了两年的时间,在计算机架构,虚拟内存与缓存以及相关的一些方面展开了一定的研究。
1968年的时候我希望供职于大学的职位,因此我申请并获得了一个贝尔法斯特女王大学的教授职位,当时的计算学院很小,由四个学术职员与四个程序员职员组成。我在那里设立了一个计算科学本科学位,以及与其它科目联合的荣誉学位。1977年,牛津大学的计算科学教授职位有空缺,我——有些不情愿的——决定回到大陆,这样会更深入主流的计算机学术生涯。所以我转到了牛津并作为数学系的一名的教授度过了22载的光阴。作为一名哲学与拉丁希腊语毕业生而言,这不得不说是令人惊奇的,然后实际上这是一段非常成功而有趣的经历。我有幸加入到一个牛人扎堆的院系,我非常喜欢他们,他们都是非常友好的朋友。
1999年,我到了学校要求退休的年限了,这时Roger Needham正在搭建微软剑桥研究院,他热诚的邀请我去那里就职。经过了仔细的思考,又与许多朋友交流了关于微软研究院的目标和组织,我对微软提供给研究者的学术自由度印象深刻。我希望我能为他们做点什么,同时通过搭建起与业界的桥梁,也为我的学术界同事们做点什么。我从未因此感到后悔。我为微软剑桥研究院工作已近十年了,我做了许多的事情,包括许多主题演讲。
我目前的主要精力投入在两个方面:一个是我的个人研究目标,它始于20年前,我把它称作统一编程理论;我还有一个学术相关的目标,那就是微软在计算机程序形式分析方面做出了卓有成效的工作,要把它与学术界在相关工作方面所做的工作建立起良好的联系。关于业界与学术界在促进科学知识与工程技术上扮演的合适的角色,我有一个明确的想法,虽然不一定是正确的。
就像从两个不同的方向来达成涅槃,业界可以从现有的东西出发,并且对于极其经济和社会价值的程序素材已有着极其丰富的经验,而这向理论家提出了挑战,因为他们必须解释他们的工作是为什么。这让我想到了我的第一次主题演讲的内容,即科学与工程的区别,为什么两者都是我们需要的,为什么在区分或者联系这两者的链条的每个环节上,我们都需要保持良好的联系。
2. 你提到微软研究院以及几个基于你的思想的相关项目。其中一个是Spec#,这是一个关于类型系统的项目,而之前它是一个用于程序的验证系统,基于你的逻辑线思想。你能给我们介绍一下这些概念,以及把它融入到类型系统与作为一个独立的系统插入到程序进行验证这两者之间的区别吗?
我第一次展开对程序正确性的形式研究,是我调动到贝尔法斯特女王大学的时候,实际上,我调到那里的动机就是因为我觉得这一领域的研究将会是长期的工作,而且我看好它实际应用的前景,并且出于它将是一项长期研究,要期待业界投入其中恐怕是不现实的。进行长期研究的理想之地莫过于是在校园,至少在1968年是这样,而直到现在我仍相信这一点,大学应当专注在原理和理论上,而不是专注任何有可能在市场上立即产生影响的技术上,因为它们体现了科学的真理。
于是我在1968年回到了校园,并且我清楚的知道,对于编程语言的这一形式研究的成果,在我1999年退休以后,才有可能应用于工业界。实际上,我这部分的预测最后证明是正确的。而我来到微软的一部分原因,是因为我能从业界内部看清,这个行业在发生什么,也许就有可能帮助实现我另一部分的预测——并不是一个很准确的预测,它比我所估计的情况更早的得到了实现。
我也发现微软的程序员在他们的程序代码中使用了断言,但并不是出于验证正确性,而是出于帮助测试的目的。因为在测试当中,断言本身也被测试了,它并当中条件宏插入到代码中,实际上是在测试期间运行时才进行求值的,因此它有能力在尽可能的离错误真正发生的最近的地方探测到程序失败的症状。我非常高兴的看到这一思想的运用,但发现它们是被测试人员所使用却也让我有一点害怕。因为测试——它天生就是——证明的敌人,如果你能证明一件事,就不需要测试了,同理,如果你能成功的测试一件事,也就不需要再证明它了。这一点可非常不妙。
我很快学会去接受这是测试与验证之间的一个非常有用的协同作用,不同的是测试程序员们所熟悉的,而验证,只有当有工作能够执行大部分的自动化验证工作的时候,才会被程序员们所熟知。我同时发现在微软内部,已经有致力于这方面工具的工作在开展了。有一个特别的工具PreFix,还有一个它的强化版叫 PreFast,这都是微软研究院内部开发的,我在微软的时候,它们已经被开发部门投入使用,用以提升对Windows代码的检查。
实际上,它不仅能够从新开发的代码检出错误,而其对于已存在的Windows代码也同样有用。它成为了开发者使用的工具集中非常有价值的一款工具。本质上讲,如你所说,它是对普通的类型系统的扩展,用以处理程序正确性更多的方面。它特别地关注在我所称的普遍错误上,即独立于程序的功能,始终都会被认为是错误的那一类型——比如空引用、去除引用以及下标溢出等。每个可能的下标溢出都会被PreFix程序所分析,以判断它是否真的会造成运行时错误。对于所探测到的一个可能的运行时错误,它会给出所有情况下的警告。
这些警告是建议性的。如果你得到一个警告,并没有任何保证这将会是一个真正的故障,而随之而来更大的问题是,真正去调试伪警告可能付出和真正的警告同样昂贵的代价,因为你必须使自己相信,尽管事实上它看起来很可疑,但这一错误将不会发生。这可是压力巨大的工作,你要搞砸了,被一些恶意软件的作者发现这个可能的错误,恶意软件的技术就会将程序带到错误可能发生之处,并利用它来达到非法的目的。如果你将警告当作一个伪警报而驳回,而它允许病毒传播的话,单个病毒就可能造成世界经济40亿美元的损失,这曾经实实在在发生过。红色代码病毒所造成的损失估计高达40个亿。
减少错误的警报显得尤为重要,同样探测所有真正的错误也十分重要,而这两者都没有任何保证。它只是纯粹建议,它也并不是真正意义上的类型,尽管它以一种稍为复杂的形式使用了类型验证的技术,尽管它用到了验证的概念,它总体上仍是工程上的折衷,以尽可能的产生最多真正的错误和最少的伪警报。然而,这一项目所做到的是,研究部门向开发部门交付可触到的好处,从它被接受的那一刻起——也花了一定的时候才被接受——开发者们就对研究部门的新主意抱有了开放的态度,而研究部门同样也有了挑战,那就是他们必须用一种不会造成过度的兴趣,不会刺激错误的期待的方式,来将他们的点子描述出来。
在微软,研究与开发两者之间的联络——管理层为此投入了大量的工作——已经真正开始发挥作用,这导致了C#,Spec#等等的产生,以及一个新的内部称为 ESP的工具,它用于检测缓冲溢出。它可以检测出100%的故障,并且可以高度的保证不会有缓冲溢出遗留在操作系统中。不能比百分之百更多了,而且还使用了验证技术来避免伪警报。
该工具实质上能保证操作系统内94%的缓冲引用是正确的,只剩下6%需要手动检测——听上去很妙,除非你意识到6%的Windows代码已经多得够呛。
但他们投入了大量的资金来进行检测,因为在以前,缓冲溢出对于公司和用户来说意味着严重的资源流失。我们逐渐在从启发式的验证方式——部分验证——向更严格的静态部分验证风格转变。我们并非保证程序整个的正确性,而只是避免结构化的错误,但我们可以看到未来的可能是,逐步的向着理想的零缺陷的软件这一目标前进。
3. 刚才的谈话中你提到了空引用与缓冲溢出。那么在程序中还有什么其它东西我们要检测的?程序中还有哪些错误我们可以或应该去检测呢?
运行程序时我们有各种各样的异常需要去避免。欧洲空间项目对于形式技术有着非常成功的应用。阿丽亚娜5型的空难事故中让他们刚起飞就失去了火箭,在此之后,对随后发射的所有火箭,他们都使用了工具对控制代码进行分析,这一工具用于检测与避免溢出——数字太大或太小(超出了范围)。我想下一阶段是能够让程序员提前指明哪些异常是完全不想发生的,特别是那些库文件的接口异常,因为良好的库文件对于调用的异常情况都有良好的文档。
你只需要看看一个库调用的前20行或者100行代码,就可以发现所有可能出错的地方。我认为如果程序员可以多一个检查的选择的话,将会非常有帮助。也许并不是所有的,但有些模块可能不会出现任何这样的异常,因为它们是以此为目的的。它们可能表达了用户代码与库文件接口在正确性与结构完整性方面的重要性。
但同时,你还想要更为严格的检查,至少能检查到程序的功能性特征。程序依赖的正确性可以被编码为断言,并使用同样的验证技巧来检查它们始终能得以满足。比如特定的参数能够保持在需求范围以内,特定的安全缺陷不会被容忍等等。最终,科学家是理想主义者,因此要达到的是能够将程序的整个功能正确性都得到条件表达。指明什么是程序员的意意图,并保证得以真正做到。
4. 函数式程序设计语言等语言中,甚至想在类型系统内指明其影响,比如Haskell。你对此有什么看法?这对于企业开发是否有益?
当然。Haskell中所用的控制作用的技巧正是函数式语言当中用到的一类技巧,用来再生命令式程序当中对行为属性的验证。有趣的是,就算是命令式语言,你也得考虑系统行为的历史方面或者对于环境作用的历史,才能明确说明什么样的行为才是正确的。作用系统在命令式和函数式语言中都很重要。
5. 当你实现软件的时候,有这样的强类型系统会有很大的帮助。微软研究院有一个Singularity的研究项目,我们听说你以某种方式参与了这一项目,你能给我们聊聊这个项目吗?
我不能说与这个项目有过很多的联系,但它是基于我过去所赞成的一项理论,那就是在通讯进程不相连,并且每一进程的本地工作空间对于其它任何进程来说完全不可访问,进程间唯一的通讯方式就是显式的输入和输出消息。在这样的环境中,我们能够最容易地对并发进行控制。这显然是先于通信顺序进程(CSP)理论就存在的思想,CSP是我的工作结晶,并且早在这一项目多年之前就作为构建操作系统的一种方法提出了,但它们面临的困难是牵涉到复制一个进程向另一进程发送的消息,从第一个下行进程的存储复制到最终接收进程的存储。
额外的复制需要额外的时间,这对于缓存存储不利,而且如果消息都很大的话,你大部分的时间都花在消息传送而不是做有用的工作上了。解决的办法是不要复制消息,而是把消息的权限从一个线程或进程传送到另一个线程或进程。所有权与指向消息的指针一起传送,而指针只用在寄存器复制一次,接收进程得到这一权限,就可以在原位读和更新消息,不用做任何复制。
在指针被送回发送进程(也许在后面的阶段),或者更可能的是,发送给操作系统的另一模块做进一步操作之前,该发送进程被保证无法查找或者读取该消息。检查没有任何一个进程能在不具有权限的情况下访问共享内存,这由影响系统完成,而编译时系统保证没有错误。我认为这在消息传送思想的应用方面是很大突破。
6. 在软件领域中,我们以某种方式建立了一些假设,并在此之上构建整个软件大厦,都没有对它们进行重新思考,无论如何,我们就是这样做的。但有的时候,我们会重新审视这些假设。你认为在整个软件栈中有很多假设值得我们重新思考吗?
我认为这些假设在现实的软件当中是普遍存在的。当流程将控制信息传递给它的时候,每一行代码都对其前一行代码做了什么和后一行代码要做什么做出了相当多的假设。使用断言的验证技术是将这些假设变为显式的一种尝试。每个断言都是一个接口断言。它定义了当控制被传递到这一点的时候,使得在该断言之前的程序能令断言为真,以及在这之后的代码能使断言为真,这是所有程序应履行的职责。
而不太舒适的方面就是,如我所说的,这些断言的大小将非常庞大。隐式断言的数量将非常巨大,程序的正确性依赖于此,但没有人会将其显式的写出来。而验证技术实际上却要求它被写下来,变得非常明确。当你查看非常严重的编程错误时,真正的解释不会是某人犯了个错,而只是他们没能够将他们的假设明晰的表达出来,所以原本在假设为真的条件下可以工作的程序,就不能工作了。
7. 你提到阿丽亚娜火箭,这让我想起这样的假设,因为它是一个模块并且在小型火箭的环境中测试是正常的,之后把它用在新的大型的火箭上,其假设与实现也是没问题的,但却忽略了考虑它是否能适用于一个完全不同的环境。是否有可能能够作出所有的断言来确保程序100%不会出现问题呢?
当你看到这些假设的大小的时候,你就会想靠人力把它们全部阐释和记录下来是否是现实的了。如果程序没有记录下这些东西,那么计算机就不能构造或检查或证明程序员是正确的。事实上,微软的开发者觉得断言有一点局限。要明确的写下一个有用的断言将会耗去很长的时间。
每个人都工作在时间的压力之下,并且我认为他们正确的评估了他们真正写下来的大部分断言不会对任何人有帮助,因为绝大部分代码在绝大多数时间都是正常工作的,投入大量的精力来将所有的断言都明确是不明智的,因此它们派不上用场。而从工程的另一方面来说,已经有足够的经验,甚至有时候是法律约束你无比彻底的文档化你的设计。你可能寄希望于没有人会去看文档,但这是确实存在的,因为一旦有什么差错,就会有调查,而如果发现事故责任是由于你没有正确的记录文档或者文档做得不够,你可能会进监狱——只是因为粗心大意。
在其它的工程领域文档化工作被视为必需的开销;而在编程工作中目前这还不是广泛接受的实践。对这一问题,越来越现实的一种解决方案是编写程序分析工具,它可以真正的推断正确标准,断言应当是什么,循环中的不变量是什么,条件判断的先决条件是什么等等。这是通过听起来较理论化的一些技术来实现的,比如弱先决条件,用来推理并区别哪些明显是程序员不想看到发生的,哪些是程序员真正想要发生的,并逐渐通过额外的假设和确保证明成立的先决条件来建立或补充代码里的断言信息。这一研究领域在近年来取得了极大的进展。
8. 比如说,在进行各种拼写检查时,有一些基于语法理论或者类似原理的机制。在谷歌公司,他们所做的是运用许多计算机来分析许多文本,从而找到需要的文本然后进行拼写检查和语法检查。有两种不同的途径来进行语法检查,两种截然不同的方式。同样的,在编程语言中,也有基于证明的系统和静态类型的类型系统,比如 Haskell以及通过观察运行时行为来进行优化和作出运行时假设的动态语言。你对于这样的区别,或者说这两种不同的手段有什么样的看法?
在运行时进行所有检查与我对断言所做的建议是类似的,你在测试的时候使用断言来探测代码里是否有回溯错误,但是逐渐的,开发者将代码中的断言保留到运行在客户的环境中,因为目前的机器已足够快,相比去掉这些断言而去冒随后可能出现的更大的风险,在客户环境中保留这些额外的安全与诊断能力是很划算的。
而对于动态语言的动态类型检查也非常类似。如果静态类型检查负担太大或者不可行,或者你需要动态类型系统额外的灵活性,那你就需要付出运行时类型检查的额外开销。这都是连贯了,而你选择的技术最终是最适合你当前需求的。换句话说,这是一个工程上的问题,而不是科学上的。
9. 你提到你有古典文学的学位。那么你是怎么投身到计算科学领域的呢?我听说你曾在俄罗斯学习过计算机科学和编程语言。能给我们讲讲你在60年代投入计算行业以前的经历吗?你是从何开始的,而在当时哪些领域还在起步阶段?
我觉得真是说来话长歪打正着啊。牛津的古典文学学位由两部分组成:四年的学位——有两年学习拉丁与希腊语言和文学,剩下的两年学习古代史和哲学。你要学习古代哲学和现代哲学。在古代哲学中要学习亚历士多德和柏拉图,而现代哲学则学习了现代逻辑实证主义思想和语言学逻辑,同时还有了解传统的哲学家思想以及英格兰经验主义论者。
我尤其为数理哲学而着迷——如何使用数学论证来让人类智慧感知到真理的确切性,这远远胜于通过一些对现实世界的拙劣观察而得出的结论。你给出的证据是证明这一关于真理的理念,对我来说着实是非常有趣的哲学思想。至少在我最后一个学期,我的哲学导师John Lucas,也同样沉迷于通过计算机的潜力来阐明一些哲学问题。
于是我静下来读阿兰图灵关于计算机程序的不确定性问题的文章,而说到底,文章是发表在Philosophical Journal Mind上的。这可真让我震撼!我当时不太理解,不过我觉得这真是神了。因此我就在想如果投身计算科学将会多么有趣,这正是我梦寐以求的那种工作。我也正是这么做的。既然我——我自以为——擅长语言,比如我通晓拉丁语希腊语以及俄语,他们让我去开发一种人工语言(而不是真正的语言)的编译器——故事也就从这里开始了。
10. 当我们尝试用计算机程序来翻译人类语言,你在什么时候把它变成人工智能的呢?这是多久的事儿?
我对人类语言翻译发生兴趣也算是机缘巧合。离开牛津后我在莫斯科国立大学学习了一年,其间我收到一封来自国家物理实验室的信,给了我一份声誉极高而资深的工作,就职于其计算科学部门从事一个将科学俄语翻译成英语的项目,并担任资深政府科研员。他们所做的工作是在图灵的老式ACE计算机设计之上进行编程—— 在当时这是英格兰所拥有的第一批计算机其中之一。
于是我开始研究俄国人在机器翻译方便所做的工作,正巧又遇上了一群正好对翻译英语成俄语,和翻译俄语成英语刚刚发生兴趣的人,他们使用某种语法分析的技术 ——实际上,我写的第一篇文章是投给了俄国机器翻译期刊,并得到发表。当然,这是用俄文,在一位朋友的打字机上敲出来的,并得到录用。我记得以前我曾经向旧金山的计算机博物馆捐了一份这篇文章的副本。
在俄罗斯学习了六个月的语言的机器翻译之后,我得到的结论是这不可行,所以我没有接受国家物理实验室的这份工作。这还有两个另外的原因:首先,他们说: “欧,恐怕不能是资深政府科研员了,实际上不能是政府科研员了。我们将任命你为技术员,我们发现你没有科学学位。你知道的,这意味着你无法成为政府科技人员的一分子。你只能是临时性的。”所以作为一名临时性的试用人员我认为婉拒这份工作也是情有可原的。
不管怎样,我还在莫斯科的时候还——正好在我归国之前——帮助我的叔叔组织了一个在莫斯科的博览会,恰好Eliot Brothers在会上展示他们的计算机。哈哈,人品爆发!因为我从未被允许参观俄国的计算机——它们可都是机密。我去了博览上赶上这台机器,我有许多时间站在它面前为英国技术人员和俄国参观者充当翻译。最后,他们要将机器运往莫斯科,顺便让我搭个便车捎我回去,这样我能帮他们应对客户和俄国人。当我回国的时候,他们就向我抛出了橄榄枝。
11. 这是否意味着你渴望成为一名科学家,但当时你不得不就职于Eliot Brother作一名工程师从而从事计算机工作?
实际上我立志当一名哲学家。直到现在你都可能会察觉到这一点,因为我所说的很多东西都是哲学化的,你不这样认为吗?当我到贝尔法斯特女王大学讲第一堂课的时候,一些观众就说“我从未想到你能就计算机讲出这么多哲学来”。而自从那时候起我一直所做的也就是这件事。
12. 你有时候会不会觉得某些研究成果要成为主流需要极其漫长的时间,而觉得烦躁呢?
当我第一次去贝尔法斯特的时候,我觉得我能从事一项研究并看着它花了20年或更多走向成熟——实际上用了30年——是件很棒的事情,这是因为我想到下面的理由,一旦这一课题可以适合业界进行研究了,业界会投入你在学术界无法比拟的资源进入这一领域,这样的话你不得不放弃该课题而另谋出路了。因此我预测关于形式化,语义和计算机程序正确性的研究会投入我毕生的精力,没想到给说中了。
现在,我注意到微软和其它公司都对程序分析技术和提升代码质量产生了浓厚的兴趣,并迈出来重要的一步,他们所投入的资源远远超过了学术界的范围。但我认为我过去的想法,即学术界是不是该停步了,是错误的;真相刚好相反,因为我注意到业界必须——而对他们来说也是正确的一步——在起步阶段,至少应当把新技术用在他们已有的产品,已有代码基础之上。
他们必须走这一步——我们在微软内部称为“熟透的果子先摘”。不管是不是捷径可循或是有杀手型应用,这终归是你第一步该做的。它可能同样会花费你十亿美元来安装新技术,它确实是,但你能得到的远远多于一点小甜头。而对于学术人员来说,有足够的空间从相反的角度出发来进行工作,为了十分长远的目标,当然,这目标意味着完全的正确性,并且完全的接受基于什么样的项目选择什么样的技术始终是个工程和商业上的决定。
而科学却始终如一,不管是作出的何种决定。科学家的目标是保证这一选择始终是可获得的,不会有人是因为科学的不确定而不愿使用技术。我想这就是科学与工程相辅相成并互为推进吧。
13. 看一下微软的动向我们就能明显的感觉到这一点,因为现在我们越来越多的听到微软推出的基于大量研究成功的商业产品,比如 F#,Spec#,Singularity以及更多有待公开的产品。你认为这会改变我们认知软件的方式吗?我们对于软件会不会来个换位思考呢?因为我们长久以来一直基于某些假设,而现在有了新的思想,新的事物产生,看起来软件行业正在经历一场改变。
科学是倾向于变革,倾向于思维转变的,它也青睐新生事物。我很乐意看见我个人的许多研究和活动都在这一方面对拓展业界与学术界的联系产生了贡献。目前迫切需要,并且在计算机科学学术领域可以看到的思维转换就是迈向规模的项目,大规模的长期项目。这是科学走向成熟的正常过程,它首先开始于科学家个人的重大发现,不管是他是自己一个人,还是来自小的实验室甚至大型实验室。但是,尽管这样的工作在一些成熟的科学分支还有用武之地,已经不是科学研究的主流形式了。
物理,天文以及近些年来的生物学,都成为了近半个世纪以来的重大科学,而其重大发现都是来自全球性的合作项目,只有大规模的长期性全球合作项目才能有此功效。CERN所建造的核加速器即是很好的一例;建造卫星,建造和使用天文望远镜——这都是现在的重大国际合作的壮举,就像以前发现人类基因组所做的一样。我想在关于程序验证领域这方面,相比以前而言,我们需要看到更多的聚合以及协作,共同规划。人们更愿意编写工具和使用其它科学家编写的工具来执行他们的程序分析并引导他们的研究走向正确的方向。
14. 您的一身都献给了计算机科学,我猜想你一定看到过一些公共的趋势,这些趋势从1960年以来都始终如一。这些趋势是什么呢,你认为未来它们还将持续下去吗?
我很遗憾的告诉你,始终如一的不是好的东西,恰恰是那些错误。60年代我们遭受的困扰是难以预测大型系统的性能,难以发现需求,难以在大型系统的模块间连贯的实现代码。而这些困难一路走来风雨兼程,仍与我们相知相伴。我想我可以说,甚至在1960年,我们就不得不与遗留代码打交道。Djkstra曾说过他感谢上帝没有让他每天上班的时候去处理遗留代码——这一点仍旧如是。
15. 您在计算机科学领域作出了许多重大贡献,比如在并发方面,您的CSP启发并影响了Algol。那么如果有的话,哪一项是您的最爱呢,你可以自豪地说:“这是我最骄傲的孩子”?
当然啦,最富有戏剧性的肯定是快速排序啦。这是我的第一个发现。我知道它值得发表,而我这么做了。当我回到学术生涯中来的时候,这肯定是我能被任命的一个主要原因。所以我不得不说,我对快速排序相当宠爱。