本访谈系列的翻译已经征得原作者的同意,转载请保留原作者和译者的链接。
Copyright © 1996-2005 Artima Software, Inc. All rights reserved
The Trouble with Checked Exceptions
A Conversation with Anders Hejlsberg, Part II
by Bill Venners with Bruce Eckel
August 18, 2003
Checked Exceptions的问题
翻译:刘晓伟
摘要
Anders Hejlsberg,C#的主架构师,与Bruce Eckel和Bill Venners谈论了已检测异常(checked exceptions)的版本(versionability)问题和可伸缩性(scalability)问题。
Anders Hejlsberg,微软的一位杰出工程师,他领导了C#(发音是C Sharp)编程语言的设计团队。Hejlsberg首次跃上软件业界舞台是源于他在80年代早期为MS-DOS和CP/M写的一个Pascal编译器。不久一个叫做Borland的非常年轻的公司雇佣了他并且买下了他的编译器,从那以后这个编译器就作为Turbo Pascal在市场上推广。在Borland,Hejlsberg继续开发Turbo Pacal并且在后来领导一个团队设计Turbo Pascal的替代品:Delphi。1996年,在Borland工作13年以后,Hejlsberg加入了微软,在那里一开始作为Visual J++和windows基础类库(WFC)的架构师。随后,Hejlsberg担任了C#的主要设计者和.NET框架创建过程中的一个主要参与者。现在,Anders Hejlsberg领导C#编程语言的后续开发。
2003年7月30号,Bruce Eckel(《Thinking in C++》以及《Thinking in Java》的作者)和Bill Venners(Artima.com的主编)与Anders Hejlsberg在他位于华盛顿州Redmond的微软办公室进行了一次面谈。这次访谈的内容将分多次发布在Artima.com以及Bruce Eckel将于今年秋天发布的一张音频光碟上。在这次访谈中,Anders Hejlsberg谈论了C#语言和.NET框架设计上的一些取舍。
Bruce Eckel: C#没有已检测异常(checked exceptions)。你是如何决定要不要把checked exceptions加入C#的?
Anders Hejlsberg: 我觉得checked exceptions有两个大问题:可伸缩性(scalability)和版本(versionablility)。我知道你也写过一些关于checked exceptions的东西,而且你也倾向于我们的看法。
Bruce Eckel: 我曾经以为checked exceptions是非常棒的。
Anders Hejlsberg: 确切的说,乍看起来它们是非常棒的,这个点子并没有什么错。我完全同意checked exceptions是一个很不错的特性。可能有问题的只是那些特定的实现。比如,checked exceptions在Java里的实现方法,我认为你只是用这一堆问题替代另外一堆问题。最终我还是不清楚你是否真的让这一切变得更容易了。你只是换汤不换药罢了。
Bruce Eckel: 关于checked exceptions,在C#的设计团队里有很多不同意见吗?
Anders Hejlsberg: 没有,我想我们的设计团队就这个问题在很大程度上是一致的。对于checked exceptions这个问题,C#基本上是保持沉默的。一旦以后有了好的解决方案——相信我,我们还在考虑这件事情——我们会回头给它添加一些适当的东西。我深信,如果你不知道什么是正确的,或者不知道前进的方向,那你最好保持沉默和中立,而不是试图设计一个框架。
如果你要一个编程新手写一个日历控件,他们经常会在心中盘算,“哦,我要写一个世界上最好的日历控件!它应该根据日历的类型而展示出多态。它应该有displayers和mungers,应该有这个有那个,还应该有其它的东西。”他们需要两个月才能发布一个日历程序。他们把所有这些基础设施都放到控件里,然后花两天时间在它上面写一个蹩脚的日历程序。他们想,“下一个版本的程序里,我会做更多这样的事情。”
一旦开始考虑如何从实际上实现他们的抽象设计中其它需要具体化的东西,他们就会发现自己的设计彻头彻尾的错了。他们把自己把逼进了死胡同,他们必须把所有的东西都扔掉。我曾经一次又一次地目睹这种事情。我是个极简主义的信徒。除非你真的要解决通用问题,不要试图为了解决一个特定的问题而整出一个框架,因为你不知道那个框架看起来应该是什么样子。
Bruce Eckel: 极限编程的程序员们说,“尽可能地做简单的事情,能正常运行就可以了。”
Anders Hejlsberg: 是的,爱因斯坦说过,“尽可能地做简单的事情,但是不要过于简单。”关于checked exceptions,我所关心的是,它给程序员戴上了手铐。你会看到程序员们捡起这些包含Throws语句的API,然后你会发现他们的代码有多么的错综复杂,再然后你意识到checked exceptions根本没有帮上他们什么忙。这在一定程度上是因为这些独断专行的API设计者告诉你应该如何进行异常处理。他们不应该这么做。
Bill Venners: 你曾经提到了与checked exceptions的可伸缩性(scalability)和版本(versioning)相关的问题。你是否可以就这两个问题阐明一下您的意思?
Anders Hejlsberg: 那就让我们从versioning开始吧,因为这个问题是显而易见的。比如说,我创建了一个叫做foo的方法,并且声明抛出异常A、B、C。在Foo的第二个版本里,我想要添加一组功能,现在foo可能会抛出异常D。我给那个方法的throws语句添加异常D导致了一个破坏性的变更,因为那个方法的既有调用者几乎肯定不会处理这个异常。
在throws语句新版本里添加一个新的异常打断了客户端代码。这有点像给一个接口添加一个方法。在你发布一个接口之后,从实际的角度来说你无论如何都不应该再改动它,因为它的任何一个实现都有可能包括你在下个版本里想要加入的方法。所以说你最好还是另外创建一个新的接口。异常与此类似,你要么创建一个全新的方法叫做foo2,让它抛出更多的异常,要么你在新的foo版本里捕获异常D,然后把D转换成A、B或者C中的一个。
Bill Venners: 但是即便是一门不支持checked exceptions的语言,这种情况下你不也是在打断他们的(客户端)代码么? 如果foo的新版本将要抛出一个新的异常,而客户端又有必要处理这个异常,但是事实上他们写代码的时候并没有预期到会有这个异常,这不还是要打断他们的代码么?
Anders Hejlsberg: 不,因为很多情况下人们并不关心(这些异常)。他们原本就没打算处理这些异常。在消息循环机制中,有一个最底层的异常处理器。这个异常处理器会弹出一个对话框说哪里出错了然后程序继续运行。程序员通过书写try finally语句来保护他们的代码,这样当出现异常的时候才能正常退出,但是他们并不真正想要处理这些异常。
至少Java实现throws语句的方法并不强制你处理异常,但是如果你真的不处理它们,它会强制要你明确指出哪些异常可以通过。它会要求你要么捕获已经声明的异常,要么把它们放进你自己的throws语句。为了满足这些条件,人们要做非常荒谬的事情。例如,他们给每个方法都加上,“throws Exceptions”。这恰恰彻底削弱了checked exceptions的特性,你只是让程序员写更多罗嗦的废话。这对谁都没好处。
Bill Venners: 也就是说你认为更为一般的情况是,相对一个通常来说深入调用堆栈的catch语句而言,调用者并不显示地以不同的方式处理异常?
Anders Hejlsberg: 很有意思的是,人们认为对于异常来说,重要的事情是如何处理它们。实际上这不是异常最重要的东西。在一个编写良好的应用程序里,以我的看法,try finally与try ctach的比例是10比1。或者在C#里,using staement (译注:参见《Applied Microsoft .NET Framework Programming》第19章) ,它跟try finally很像。
Bill Venners: finally语句里都有些什么东西呢?
Anders Hejlsberg: 在finally语句里,你保护自己免受exceptions的干扰,但是你并不真正处理它们。错误处理会放在其它的地方。在任何类型的事件驱动的程序里,诸如时髦的UI程序,你通常会围绕你的主消息循环泵放上一个异常处理器,只有当它们跃出那个范围你才处理异常。但是要让自己全身而退你得确信是通过释放已经占有的资源等等来达到目的的。谁都不想在一个程序的100个不同地方处理异常并且弹出错误对话框。假如说你想要改变一下弹出对话框的方式那可怎么办呢?这可糟透了。异常处理应该被集中在一个地方,而且当异常向外传递给它的处理程序的时候你只要保护好自己的程序就可以了。
Bill Venners: checked exceptions的scalability问题是指什么呢?
Anders Hejlsberg: Scalability问题多多少少和版本问题是有关系的。从小的方面来说,checked exceptions是非常有诱惑力的。只要用一个很小的例子,你就能展示给大家说你已经检验到捕获了FileNotFoundException
异常,这不很棒么?是的,当你只是调用一个API的时候这样做没有问题。但是当你开始构建大的系统而需要和四五个不同的子系统打交道的时候问题就来了。每个子系统抛出4到10个异常。现在,每次当你沿着这个聚合而成的阶梯往上的时候,你的下面就有了指数级的异常层次(hierarchy),这些异常是你必须要处理的。最终你不得不定义40个可能抛出的异常。一旦你要把这个系统与另外一个子系统聚合在一起,在你的throws语句里就有了80个异常。异常的数量会失控般的激增。
从大的方面来说,checked exceptions变成了让人恼火的东西,人们试图完全绕过这个特性。他们要么到处写成,“throws Exception
”;要么——我记不清已经见到过多少次了——写成,“try, da da da da da, catch curly
(花括号)
curly
(花括号)
”他们想,“以后我会回来处理这些空的catch语句”,当然他们再不会回来补上。在这些情况下,从大的方面来讲,checked exceptions确实降低了系统的质量。
所以说,当把以上这些问题都考虑在内的话,对我来说,在把类似于checked exceptions的机制加入C#之前,我们可得需要更多的考虑。但是尽管如此,知道哪些异常可以被抛出、以及用一些工作来检测这些异常,仍然毫无疑问有着巨大的价值。我不认为我们可以很快构建出牢靠和易用的规则,它要么是一个编译错误,要么不是。但是我想我们应当可以就分析工具做一些文章,侦测可疑的代码,包括未被捕获的异常,进而为你指出那些潜在的漏洞。
Anders Hejlsberg访谈的下一部分将会在(2003年)9月8号,星期一贴出来。如果你想收到Artima.com上新文章每周简报的电子邮件,请订阅Artima Newsletter。