Anders Hejlsberg访谈:Inappropriate Abstractions

Copyright © 1996-2005 Artima Software, Inc. All rights reserved

Inappropriate Abstractions
A Conversation with Anders Hejlsberg, Part VI
by Bill Venners with Bruce Eckel
December 12, 2003

翻译:http://blog.csdn.net/lxwde

摘要

Anders HejlsbergC#的主架构师,与Bruce EckelBill Venners 谈论了试图让网络透明的分布式系统,以及试图屏蔽掉数据库的对象——关系映射。C#的产品经理Dan Fernandez以及C#编译器的程序经理Eric Gunnerson也参见了这次讨论。

Anders Hejlsberg,微软的一位杰出工程师,他领导了C#(发音是C Sharp)编程语言的设计团队。Hejlsberg首次跃上软件业界舞台是源于他在80年代早期为MS-DOSCP/M写的一个Pascal编译器。不久一个叫做Borland的非常年轻的公司雇佣了他并且买下了他的编译器,从那以后这个编译器就作为Turbo Pascal在市场上推广。在BorlandHejlsberg继续开发Turbo Pacal并且在后来领导一个团队设计Turbo Pascal的替代品:Delphi1996年,在Borland工作13年以后,Hejlsberg加入了微软,在那里一开始作为Visual J++windows基础类库(WFC)的架构师。随后,Hejlsberg担任了C#的主要设计者和.NET框架创建过程中的一个主要参与者。现在,Anders Hejlsberg领导C#编程语言的后续开发。

2003730号,Bruce Eckel(《Thinking in C++》以及《Thinking in Java》的作者)和Bill VennersArtima.com的主编)与Anders Hejlsberg在他位于华盛顿州Redmond的微软办公室进行了一次面谈。这次访谈的内容将分多次发布在Artima.com以及Bruce Eckel将于今年秋天发布的一张音频光碟上。在这次访谈中,Anders Hejlsberg谈论了C#语言和.NET框架设计上的一些取舍。

·        第一部分:C#的设计过程中, Hejlsberg谈论了C#设计团队所采用的流程,以及在语言设计中可用性研究(usability studies)和好的品味(good taste)相对而言的优点。

·        第二部分:Checked Exceptions的问题中, Hejlsberg谈论了已检测异常(checked exceptions)的版本(versionability)问题和规模扩展(scalability)问题。

·        第三部分: 委托、组件以及表面上的简单性里,Hejlsberg 谈论了委托(delegates)以及C#对于组件的概念给予的头等待遇。

·        第四部分:版本,虚函数和覆写里,Hejlsberg解释了谈论了为什么C#的方法默认是非虚函数,以及为什么程序员必须显式指定覆写(override)。

  • 在第六部分里, Hejlsberg以及C#团队的其他成员谈论了试图让网络透明的分布式系统,以及试图屏蔽掉数据库的对象——关系映射。C#的产品经理Dan Fernandez以及C#编译器的程序经理Eric Gunnerson也参见了这次讨论。  

松耦合的分布式系统

Bill Venners: O’Reilly网站上的一篇访谈里你说,“当一开始坐下来设计.NET框架的时候,我们往回退了一步,研究了一下Web上到底发生了哪些事情。它已经变成了松散连接的、分布性非常强的世界,我们试图弄明白这对于底层的编程模型有何影响。于是我们从头开始设计的时候就假定分布式程序是以松散连接的、无状态的(stateless)形式来构建的,这种形式能够给你极大的可伸缩性(scalability)。你只要在规模上进行伸缩,多拿一些部件把它们插上就可以了。 一旦你做了这个基本的假设,所有一切都随之改变了。”这个假设到底改变了什么呢?

Anders Hejlsberg: 5年前或者10年前,关于如何构建分布式系统,占统治地位的思想是CORBAIIOP以及对象请求代理(object request brokers)。那时候甚嚣尘上的观点是把整个世界都看成是对象,特别地,再有一些基础设施(infrastructure)用以掩盖这些对象是分布在不同地方的这个事实。近乎异想天开的想法是,你只要写Object obj = CreateMeAnObject(),然后调用 obj.ThisMethod() obj.ThatMethod(),而不用知道这个对象是在泰国还是隔壁房间,或者是在同一个进程里。这种编程方法的问题在于:在同一个进程里它跑得好极了;跨越多个进程也跑得不错;在一个小的intranet里也还行;但是在更大的空间里它就完全糟透了。

如果你隐藏掉消息穿越网络这个事实,并且不知道它们什么时候穿越网络,最后你得到的只能是唠唠叨叨的会话。于是在一瞬间,即使是以光速传递消息对你来说也成了个大问题。你不应该在一个对话里使用一个在纽约的对象,调用obj.LetMeGetX()obj.LetMeGetY() obj.LetMeGetZ()不,你应该写成obj.LetMeGetXYAndZ(),然后在一个数据块(chunk)里返回所有东西。但是除非你能让大家明白他们是在构建分布式系统,否则你就不可能真正做到这一点。换句话说,你不应该试图假装一个远程对象就是本地对象,因为它们之间存在不同之处。这也正是web services值得称道的一个地方。

而且,web services是跑在我们知道可伸缩的已有的基础设施之上的。基于HTTPweb services使用的是和我们每天都要用到的浏览器完全一样的基础设计,只不过它是机器到机器的通信罢了。我们非常清楚应该如何对其进行规模上的伸缩。如果我们知道应该如何进行扩展,这就够了。那为什么不理用它呢?Web services就是这么做的。而另一方面,我们对于如何针对CORBA系统随着地理范围的增加进行规模扩展所知甚少。我们不会这么做。因为关于这方面的知识很少,而且我也从来没有听说过有谁做得特别成功。

无状态的方式(The Stateless Fashion

Bill Venners: 刚才你说,“分布式程序都是以松散连接的无状态的方式构建的。。。。。。”,“无状态”是指什么呢?

Anders Hejlsberg: 如果你通过远程对象(remote-objects)的方式进行编程,无论什么时候初始化一个新对象,你都有可能最终得到一个指向远程对象的代理,而远程对象实际上在分布式系统的某台机器上的内存里被初始化。只要你在本机持有(远程对象)的引用,这就足以让远程对象所在的机器处于活动状态。这有可能让你进入非常长的事务处理——这些事务处理可能让远程的机器长时间内处于某种状态。这对于失效转移(failover)的情形来说是个问题,对于系统伸缩性来说也是个问题,因为你不可能很容易地把它们弄成分布式系统。一旦你连接了远端对象所在的机器,每次有调用进来的时候,你都要回到那台机器。

而通过HTTP,从某种意义上来说,你不得不解决HTTP本来就是无状态的这个问题。因为系统并不记录有关通道(channel)的信息——通道自己也没有状态——你不得不把自己的系统设计成每当有请求进来的时候它都可以被自由地路由给任何可以取到它的状态的CPU,然后这个CPU迅速处理一下再把它送回来。这样下次请求来的时候你就可以处理别的了。

Bill Venners: 对于服务器来说,并非一定的是无状态的。状态都被保存了。

Anders Hejlsberg: 服务器上有状态,但是分布式机制并不维持任何状态使其处于活动之中。

Bill Venners: 我不确定自己是否理解了两者的区别所在。两种情况下都存在状态。分布式机制不包含状态有什么好处呢?

Anders Hejlsberg: 很有意思。在某种意义上,web service替你初始化一个新的对象,然后持有它,并调用它的方法。Web service只是一个入口点。所有要进入内部的状态必须由你传入。所有要传出来的状态,由web service传回给你。然后它自己就把这个状态忘掉了。这与在本质上保持对象处于活动状态是不一样的。Web service对于会话(session)没有提及。

Bill Venners: 在标准里面是没有提及session

Anders Hejlsberg: 是的。

Bill Venners: 但是通常会有一个会话位于服务器上。

Anders Hejlsberg: 当然。但是最终由你来决定这个会话如何工作。我们不会告诉你说必须有一个特定类型的会话概念,这个概念要求客户端初始化一个新的对象,只要它们还持有这个对象,在服务器上对象的状态就是活动的。你最好自己想办法让它工作。

对象-关系映射(Object-Relational Mappings

Bruce Eckel: .NET框架是如何支持对象持久化的

Anders Hejlsberg: 没有哪种对象持久化的方法可以满足所有人。有时候你想要持久化是因为你想把一个对象on the wire,把它送给另一个线程,然后马上把它从wire上拿掉。对于这种情况,你想要的只是二进制序列化,而且你大概也不会关心版本问题。对于更长期的持久化,你可能想要它对版本化支持更好一些。这样你就能够用1.0版本的程序写一个对象,然后用2.0版本的程序来读入并且获知这个对象。这种情况下,你会想要牺牲一些数据表示上的效率以解决版本问题。有时候你想要把对象存入数据库并且对它们进行查询。这时候,你真正想要的是对象——关系(O/R)映射。针对以上这些情形,在.NET里我们各提供了一种解决方案,并且它们还在不断改进之中。

Bruce Eckel: O/R mappings最大的问题是什么?

Anders Hejlsberg: 所有这些O/R mappings的命运通常取决于它们的缓存策略(caching policies)是否足够灵活,它们中的大多数都不够灵活。在.NET里,我们的确花了很大力气以便让缓存策略能够完全由你来控制,否则就不采用缓存策略。很多时候,你只是想要做一次查询并且记下查询结果。你会把结果当作对象来使用,但是你不想让基础设施帮你缓存这些结果,否则下次你在请求这个对象的时候,得到的还是同一个对象。很多系统只能以上述方式进行操作,导致的后果就是可怕的性能上的负担,而通常你并不需要这些。比如说,在一个中间层,大多数时候你并不关心缓存,因为你只是在处理一些进来的HTTP请求,而这些请求在被处理之后就立即消失了。那么为什么还要缓存呢?

Bruce Eckel: 也就是说缓存应该是在你需要的时候可以要求这么做,而不是默认情况下必须使用它。

Anders Hejlsberg: 是的。大多数O/R mappings或多或少都有这个问题,它们一上来就着手缓存和引用识别(referential identity)的问题。如果你请求要一个特定的Customer,然后得到一个Customer对象,下次你请求要那个Customer的时候得到的还是同一个对象。这可是个相当棘手的问题。这么做需要一个巨大的哈希表,用以存储你所用到过的所有对象。

Bill Venners: 为什么我要关心它们是否是同一个对象呢?

Anders Hejlsberg: 比如说你取到了一个CustID100Customer对象。在一个面向对象程序的内部,如果你在某个查询里请求这个Customer,并且随后在另外一个查询里也请求这个对象,第二次请求的时候,你期望要得到的是什么呢?

Bill Venners: 一个在语义上与我第一次得到的对象相等的Customer对象。

Anders Hejlsberg: 你期望得到的是同一个引用么?

Bill Venners: 我觉得这并不是我所关心的,只要它们在语义上是相等的就可以了。

Anders Hejlsberg: 真的吗? 因为它会导致你的程序以完全不同的方式工作。你是把Customer当作仅仅有一个实例的对象呢,还是把你所操作的那些对象当作从数据库来的拷贝呢? 大多数O/R mappings试图造成一种假相,那就是只有一个CustID100Customer对象,而且字面上看就是那个customer。如果你得到这个Customer并且给它的一个field设置一个值,那么现在你就改变了这个对象。与上述情况作为对照,你改变的是customer的这个(你自己的)拷贝,而不是另外一个拷贝。如果两个人都更新这个对象的两分拷贝,谁先更新(或者谁后更新),谁的更新就生效。

Bruce Eckel: 没错,如果你确实会碰到这些麻烦,那还不如透明些好。

Anders Hejlsberg: 很有意思。这让我想起了前面我们谈到的CORBA试图制造出一种假相,让分布式程序看起来并非是分布式的。我觉得也是同样的道理。你可能想要这种假相,假装数据并不在数据库里。你可以拥有这种假相,但是得付出一定的代价。

Bruce Eckel: 对于CORBA,他们试图制造一种假相,好像根本就没有网络存在。对于Jini,他们说,“不,网络的确存在。我们必须在某一层面上承认它的存在,否则一切就变得过于复杂。”对于设计者来说,关键是你在什么地方承认网络确实存在。你在哪个层面说,“这里就是我们经常必须要看到的边界。”我想类似的问题也存在于O/R mapping。挑战在于我们如何确定哪些是合适的抽象。

Eric Gunnerson: 有个大问题:你需要这些抽象吗? 在很多情况下其实并不需要。我们在当前.NET remoting的实现上(这个实现试图做到透明)也碰到了一些类似的问题。大多数人都说,“Yeah,我知道我正在做的是remoting。我知道对象位于远端。不要费尽心机试图把它弄得看起来像个本地对象。”

Anders Hejlsberg: 这当然会更好,因为这么一来使用者就会深入思考一下有可能会发生的事情。作为设计者,你应该尽力赋予用户这种能力。

Bruce Eckel: 也就是说你尽力把抽象放在合适的层次上,这样用户为了让程序跑起来,就不会因为错误的抽象而陷入巨大的麻烦。

Eric Gunnerson: 错误的抽象带来的问题是,你没办法摆脱它。在实践中,对于类设计者来说,即使是针对他们的设计可能会被应用的场景做出合理的猜测也是非常困难的,更不用说每种应用的相对频率了。你可能会以为你的用户想要透明性,因为这可以让他们做一些够酷的事情,于是你就实现了透明性。但是如果后来证明99%的用户从来都不关心透明性,猜猜看会发生什么?那些人可都是交了钱的。

Dan Fernandez: 另一个问题是,许多开发者都想像敲图章一样把同一种方法随处应用。人们会说,“好吧,既然有一个object-relational mapping的方法,那我们的程序里所有的东西毫无疑问都要用它。”在某些地方,它可能是有用的,但是另外一些变化幅度很大的东西——比如说股票交易系统——你可能不会真的想要一个持久层。但是因为你认为它们是解决问题的一个方法,最终你还是使用它。对于某些问题,object-relational mapping确实是好的解决方案,但是有时候人们想一劳永逸,认为它是所有问题的解决方案。这才是它真正给人们带来麻烦的地方。

Bruce Eckel: 但是你能理解这是为什么,对吧? 理由就是,我现在只需要学习一种持久化模型就可以到处使用了。

Dan Fernandez: 确实是这样.

Bruce Eckel: 解决这个问题的方法可能是使用某种类型的接口,然后根据它的使用情况选择不同的实现。通过这种方法,我只要学习一个接口,然后要么选择一个实现,要么让系统根据我所调用的方法帮我选择一个实现。

Eric Gunnerson: 当然可以,接口本身也是另外一种抽象。

反馈

对本文所描述的设计原则有自己的观点么?那么请到News&Ideas论坛讨论这篇文章, Inappropriate Abstractions.

资源

Deep Inside C#: An Interview with Microsoft Chief Architect Anders Hejlsberg:
http://windows.oreilly.com/news/hejlsberg_0800.html

A Comparative Overview of C#:
http://genamics.com/developer/csharp_comparative.htm

Microsoft Visual C#:
http://msdn.microsoft.com/vcsharp/

Dan Fernandez's Weblog:
http://blogs.msdn.com/danielfe/

Eric Gunnerson's Weblog:
http://blogs.msdn.com/ericgu/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值