我们每天都在使用的Internet里有一个小魔法。就是internet基础协议里的TCP协议。
TCP传输数据是可靠的。具体说来:你通过TCP传输数据,那么,数据是肯定会传输成功的并且顺序不会乱也不会错误。
我们使用TCP协议来获取网页、发送电子邮件。就连骗人钱财的邮件它都准确的送到。好玩吧。
TCP的反面是不可靠的传输协议,名字叫IP。没有人会打包票数据一定送达,可能在到达之前就已经混乱了。你发了一堆消息,如果只有一半送达,请淡定,它们中好一部分可能顺序已经乱了,也有可能消息的内容变化了,天知道会变成什么,或者是:猩猩的照片、或者看起来就像是来自台湾的垃圾邮件的标题。
当然,最神奇的是:TCP是基于IP的。即:可靠的TCP传输数据是使用了不可靠的IP。
为了解释,想像这样一个素材,虽然有点可笑,但是道理是相通的。
假想我们把ac放到车里,准备从王府井送往鸟巢。
有些车在二环上就翻了,可怜的ac也没有了。有些ac在路上搞了非主流,把自己弄的面目全非,到了鸟巢也进不去了。大部分的ac由于走的路线不同,到的时候前前后后,混乱不堪。
现在我们新开一个快递服务:至尊快递。至尊快递担保:把ac送到,按顺序,完整的。神奇的事情是:至尊快递除了用靠不住的车和不确定的路线来送ac,就别无选择了。
至尊快递需要检查每一个到达的ac,如果变形了,就给王府井打电话,重发一个ac。如果顺序不对,就重新排队。
如果市区里出现外星人强行阻断交通,至尊快递就改道先去海子公园,在绕到鸟巢。并且,至尊快递也不告诉鸟巢外星人的事情。
对于鸟巢来说,好像ac就是比平常来的晚了点,但是,它们一定不知道外星人来捣乱。
这就是TCP的神奇之处。计算机科学家更喜欢把这个叫做“抽象”:把复杂的隐藏起来,让它看起来简单。其实,很多程式码都是基于“抽象”的。
什么是字符串库?这样的库让操作字符串就像操作数字一样。什么是文件系统?把存储bit位的磁盘封装起来,就像是分层的文件夹一样,文件夹里包含了单个的文件,这些文件由一个或多个字符组成。
回到TCP,之前我给你讲的假话,已经如天上的鸭子,飞过了(这里我是这样理解的,不知道原文的意思是不是这样),这个谎言让你快受不了了。我说过:TCP保证你的消息送达,其实,它做不到。
如果你的宠物蛇把你的网线咬断了,你的计算机就收不到IP包,这种情况下,TCP也不能做任何事情,你的消息也不会送达。
如果你骂楼主是SB,楼主把你的计算机配置到一个超载的HUB上,你的IP包只有随机通过了,TCP也可以工作,不过肯定是龟速了。
这就是我说的“抽象漏洞”。TCP想提供一个基于不可靠的网络的完全的抽象,但是,有时候,网络漏洞会越过抽象,你会说抽象也不能提供完全的封装(我是这样理解的).这只是我的抽象漏洞定理的一个例子:
所有不证自明的抽象都是有漏洞的。
抽象机制倒下了。有时是一点点,有时太多了。漏洞太多了,事情就不妙了。只要你拥有抽象,这种情况就会发生,如下:
操作二维数组的时候,横向或是竖向的操作性能是不一样的,就看在存储器里如何布局了。在按行操作或是按列操作的时候,有可能会导致页面访问失败,而这会是非常慢的。就算是在汇编程序里,可以认为有很大的线性的内存空间,但是,虚拟内存是一个抽象的概念,页失败的话这个抽象就失效了,这时候处理的时间会比其它的储存形式慢。
SQL语句就抽象了查询。你只需要定义你想要查找的,不用关心查找执行的细节。但是,有时候,SQL查询会比想象中的耗费时间。著名的例子就是:如果你的查询条件是“where a=b and b=c and a=c”会比查询条件是“where a=b and b=c”快,而这两者的结果是一样的。你只需要关心查询结果而非执行过程。但是,当抽象失效并且引起性能恶化的时候,你只有拆开查询分析器,研究到底神马情况,并且研究如何才能提升性能。
尽管网络库如:NFS、SMB可以让你操作远程的文件就像是在自己的计算机上一样。但是,如果网上变慢或是断线了,身为码农,你该咋办?把“远程文件映射到本地”的抽象失效了,在Unix里有活生生的例子:你把用户的home文件夹挂载到NFS的磁盘上(一个抽象),用户又创建一个.forward文件夹来把email转存到其它地方(又一个抽象),新邮件到达的时候,NFS刚好罢工了,由于它的罢工,挂载给它的文件夹也就找不到了,错误发生了,你的email都没有了。
C++ String类是希望你处理数组相对简单,就像是处理整型那样,它抽象了“字符串很难处理”。所有的C++ String类都重载了+操作符,这样你可以这样来连接两个字符串: s+"bar"。你知道吗:无论有多难,地球上就没有那个String类允许你这样:"foo" + "bar",因为C++里就没有语义上的字符串,它们其实是char*。这个抽象带来的漏洞你是没法补的。(有趣的是,C++的进化史就是补String抽象带来的漏洞的历史。他们为嘛不在C++里添加一个原生的字符串类?我不知道耶。)
下雨的时候,你不可以开太快,就算你有挡风玻璃有各种装备,这些装备可以让你忽略下雨这个事实(这些装备把天气抽象了),但是,你还是担心路滑(或是滑水效应),雨很大的时候,你看不远。你在雨里还是开车慢,因为天气不会被完全抽象,因为抽象漏洞法则真实存在。
抽象漏洞法则之所以是个问题,是因为这个抽象并没有期望的那么简单。当我教C++的初学者的时候,我不教他们char*及指针运算,直接教STL字符串。但是,总有一天他们会这样写:"foo" + "bar",或是奇怪的代码,我还是需要教他们char*。而且,某日他们需要搞明白:char*、指针、Unicode、wchar_t、TCHAR头文件,否则他们没法调用参数是 OUT LPTSTR 这样的window API,么么,这些都是漏洞。
教某人COM编程,告诉他们如何使用Visual Studio 向导、代码生成器,如果出了问题,他们就不知道如何处理,也不知道如何修复这个bug。我需要告诉他们IUnknown 、CLSIDs 、ProgIDS 等等。噢,不人道呀。
教ASP.NET编程,就告诉他们双击控件,然后就添加代码。而不是教他们ASP.NET在处理点击超链接和点击按钮有什么不同。ASP.NET的设计者需要隐藏HTML的外表(我的理解——译者),你不可以通过超链接来提交表单。他们在javaScript里实现,并且放到到onclick的处理代码里。抽象的漏洞。如果我们禁用了javaScript,程序就出错了,如果我们的码农不明白这个抽象原理,他们就无法指定问题到底在哪里。
抽象漏洞法则说明:任何时候,大牛发明了一个工具可以提高效率的时候。很多人说:“我先人工的操作,然后通过代码来节约时间”。代码生成工具就是这样想的,它抽象了一些东西,但是,所有的抽象机制都是有漏洞的。唯一可以处理漏洞的方法就是知道抽象的原理,都抽象了些什么东西。抽象可以节约我们工作的时间,但是,节约不了我们的学习时间。
这一切都似是而非,我们有更高级的编程工具,更好的抽象,然而成为专家就更难了。
我在微软实习的时候,我给Macintosh写了一个字符串库。一个常见的任务:写一个strcat函数,返回一个指向新字符串结尾的指针。几行代码。我做的事情记录于K&R(这是一本讲c语言的小册子)
如今,为了做CityDesk,我需要知道Visual Basic、COM、ATL、C++、 InnoSetup、 Internet Explorer internals、 正则表达式、DOM, HTML, CSS, and XML。但是,我还是需要知道K&R里的东西,否则我就完蛋了。
10年前,我们希望有一个更高级的编程范式可以让编程更简单。我们创造的抽象机制,让我们可以设计更复杂的软件,如:GUI和网络程序,在10年或是15年前是无法想像的。这些伟大的工具,如:OO编程语言,让我们飞快的工作,但是某天,我们为了排查一个抽象带来的漏洞,一查就是几个星期。你找一个写VB的码农,如果他只会单纯的写代码的话,当VB的抽象出现漏洞的时候,他就会傻眼。
抽象漏洞会拖垮我们。
原文地址:http://www.joelonsoftware.com/printerFriendly/articles/LeakyAbstractions.html
你也可以在这里看:http://blog.sina.com.cn/s/blog_5fcf62ff0101haar.html