抽象渗漏法则

作者:周思博(Joel Spolsky)
译:Paul May 梅普华
Monday, November 11, 2002
属于Joel on Software, http://www.joelonsoftware.com

你每天不可或缺的Internet里有个关键的小魔法,这个魔法就在TCP通讯协定这个internet的基础协定里。

TCP是一种_可靠_ 的资料传输方法。我说可靠是指如果用TCP在网路上传一个讯息,讯息一定会到,绝不会乱掉或坏掉。

TCP的用途很多,比如抓取网页资料或传电子邮件都是。由于TCP这么可靠,连那些挪用钱的东非人电邮(译注:指有阵子常见到的骗人信)都能完整无缺的到达,真是好笑。

相对的有另一种叫IP的_不可靠_ 资料传输方法。IP不保证资料会传到,就算到了资料也可能会乱掉。如果你用IP传送一堆讯息,很可能只有一半的讯息到达,而且其中还有一些到达的顺序和原先传送时的顺序不同,另外可能有几个讯息的内容会变掉,可能变成可爱的猩猩宝贝照片,更可能变成一堆看不懂的垃圾,看起来就像台湾垃圾信的标题一样。

这里就是魔法所在:TCP是架在IP上面的。换句话说,TCP不得不靠_一个不可靠的工具_ 想办法可靠地传送资料。

为了说明这的确是个魔法,想想下面这个本质上相同(虽然有点滑稽),来自真实世界的情节。

想像你有个方法把演员由百老汇送到好莱坞,基本上就是让人坐上车后开车横越国家送过去。有些车会出车祸让可怜的演员挂掉。有时候演员在路上喝醉了就去剃光头或刺纳粹刺青,结果变得太丑而不能在好莱坞工作。另外由于走的路线不同,演员到达的顺序常会跟出发的顺序不一样。现在想像有个叫好莱坞快递的新服务,可以把演员送到好莱坞,并且保证演员一定会(a)到达,并保证(b)顺序不变而且(c)状态完美地到达。神奇之处在于好莱坞快递除了原本的车子以外,并没有新的运送方法。好莱坞快递的作法是在每个演员抵达时检查演员的状况,如果状况不佳就打电话请公司把该演员的双胞胎送来。如果演员到达的顺序不对,好莱坞快递会照正确顺序重新排好。如果51区有架大幽浮在内华达的高速公路上坠毁阻断了交通,预定走这条路线的演员就会改走亚历桑那州,好莱坞快递甚至不会把事情告诉加州的导演。导演只会觉得演员来得比平常慢,他们甚至不会 听到 幽浮失事的消息。

TCP的魔法大致上就是这样。这种作法常被电脑科学家称为_抽象_ :把复杂许多的东西隐藏起来的一种简化动作。结果很多电脑程式的设计都是在建立抽象机制。字串程式库是什么?它是一种伪装,假装电脑能像处理数字一样轻易的处理字串。档案系统又是什么?也是一种伪装,假装硬碟并不是一堆不停旋转,可以储存位元的磁性碟片,而是一个有着层层目录的阶层式系统,可以存放一个个由一或多个位元组字串构成的档案。

把话题拉回TCP。稍早为了让事情单纯一点,我撒了一个小谎,而且现在有些人可能会因为这个谎气得头上冒烟。我说过TCP保证你的讯息会到达,其实并不会。如果你养的蛇把连接电脑的网路线咬断了,就 没有任何 IP封包可以通过,这时候TCP当然也不可能让你的讯息抵达。如果你惹毛了公司的系统管理员,他们为了报复就把你接到已经超过负荷的集线器,因此只有部份的IP封包能通过,这时候TCP是会动,不过一切都会变得很慢。

这就是我称之为_抽象机制有漏洞_ 的状况。TCP试图提供一个完整的抽象机制,想隐藏底下不可靠的网路,不过有时候网路会渗漏越过抽象机制,这时就会觉得抽象其实并不太能真的提供保护。这只是我所谓「抽象渗漏法则」的一个例子而已:

所有重大的抽象机制在某种程式上都是有漏洞的。

抽象会失效。有时候轻微有时候很严重,反正就是有漏洞。事情会因而出错,而且当你有抽象机制时到处都可能会发生。下面有一些例子。

1. 像扫描一个大的二维阵列这么简单的动作,是由水平方向或垂直方向扫描都会严重影响效率,影响的大小依「木纹」(译注:二维阵列排列的方式)的方向而定,某个方向可能比另一个方向多产生许多的分页失败,而分页失败是很慢的。虽然写组合语言的程式师应该可以假设自己拥有可连续定址的记忆体空间,不过虚拟记忆体表示这种假设只是种抽象机制而已。当出现分页失败时或是某些记忆体读取时漏洞就会出现,处理时间会比其他记忆体慢几毫微秒。
2. SQL语言希望把资料库查询的程序抽象化,让你只要定义想要的东西,查询动作的细节就交由资料库去处理。不过在某些状况下,有些SQL查询比逻辑上相等的查询慢上几千倍。这有个很有名的例子,在某个SQL伺服器用"where a=b and b=c and a=c"来查询,会比用"where a=b and b=c"快上许多,可是查询的结果其实是一样的。照道理只要指定规格,并不需要在意程序。可是有时候抽象机制会失效并导致很差的效率,于是你就得跳出来用查询规划分析器找出问题,然后想办法加快查询。.
3. NFS或SMB之类的网路程式库,能让你「像」处理本机档案一样地处理远端机器的档案。有时候连线速度会变得很慢或是断线,这时远端档案就不再像是在本机上了,而身为程式师的你必须加程式码来处理这种状况。「远端档案和本地档案一样」的抽象机制[出现漏洞](/wiki/The_Joel_on_Software_Translation_Project:%E9%8C%AF%E8%AA%A4%E6%83%B3%E6%B3%95 "The Joel on Software Translation Project:错误想法")了。这里有个Unix系统管理员的具体例子。如果你把使用者的home目录放在用NFS挂入的磁碟上(一种抽象机制),而使用者建了一个.forward档案把他们的电邮全部转寄到其他地方(另一种抽象机制),如果新邮件进来时NFS伺服器停掉了,由于找不到.forward档讯息并不会被转寄出去。这个抽象机制的漏洞就真的会把一些讯息丢掉。
4. C++字串类别应该能让你假装字串是个第一级(first-class)资料。它们尝试把[「字串很难处理」](/wiki/The_Joel_on_Software_Translation_Project:%E5%9B%9E%E6%AD%B8%E5%8E%9F%E9%BB%9E "The Joel on Software Translation Project:回归原点")这个事实抽象掉,让它使用上像整数一样容易。几乎所有C++字串类别都会多载+运算子,才能把字串连接写成**s + "bar"** 。不过你知道吗?不过怎么努力,世上还是没有C++字串类别能让你写成**"foo" + "bar"** ,因为C++里的字串常数一定是char*,绝对不会变成字串。这个抽象机制呈现一个程式语言本身不给补的漏洞。(有趣的是,C++随时间演进的历史,可以描述成尝试用修补字串抽象机制漏洞的过程。他们为什么不直接在语言本身加个原生的字串类别?这实在让我搞不懂。)
5. 再来就是下雨天时开车没办法开得和平常一样快,虽然车上有挡风玻璃雨刷有头灯有车顶还有暖气,这些装备应该是让你可以忽略下雨这个事实(他们把天气抽象化了),不过看吧,你还是得担心天雨路滑,有时候雨甚至会大到你看不远,所以在只好慢慢地开,因为基于抽象渗漏法则,天气永远不能完全被抽象化。

抽象渗漏法则会造成问题的原因之一,是因为它说明了抽象机制并不真能照原构想简化我们的生活。当我想训练某人成为C++程式师时,最好能完全不教char和指标运算,直接去学STL字串。问题是总有一天他们会写出 “foo” + “bar” 这样的程式然后看到怪事出现,于是我就得停下来教他们有关char的事情。他们也可能会试着呼叫某个需要OUT LPTSTR参数的Windows API函数,于是又得把char*、指标、Unicode、wchar_t以及TCHAR含入档搞懂,才会知道如何呼叫。而这些全都是漏洞。

在教COM程式设计时,最好只要教学生如何使用Visual Studio的精灵和各个程式产生功能。不过万一出了任何问题,他们根本不会知道怎么回事,也不知道如何除错或回复。我还是得教他们IUnknown和CLSID还有ProgIDS以及。哦,饶了我吧!

在教ASP.NET程式设计时,最好只要教学生可以在元件上双击,然后就能撰写使用者点击该元件时在伺服器执行的程式。不过处理超连结( < a>)点击事件的HTML程式,和某个按钮被按时的处理程式是不一样的,而ASP.NET实际上是把这之间的差异抽象化了。问题来了,ASP.NET的设计者必须把HTML无法由超连结传送表格的事实隐藏起来。他们的做法是在超连结的onclick产理加上几行JavaScript程式。不过这种抽象机制也有漏洞,如果使用者关闭JavaScript功能,ASP.NET的应用程式就不能正常的运作了,万一程式师又不了解ASP.NET抽象掉什么东西,根本不可能知道出了什么问题。

抽象渗漏法则表示,当某人发明一套神奇的新程式产生工具,可以大幅提升效率等等,就会听到很多人说:「应该先学会如何手动进行,然后才用这个神奇的工具来节省时间。」 程式产生工具假装抽象掉某些东西,和其他所有抽象机制一样都有漏洞,而唯一能适当处理漏洞的方法,就是弄懂该抽象原理以及所隐藏的东西。所以抽象机制虽然替我们节省了工作的时间,不过学习的时间是省不掉的。

而这一切都似非而是地表示,即使我们拥有愈来愈高阶的程式设计工具,抽象化也做得愈来愈好,要成为一个纯熟的程式师却是愈来愈难了。

我第一次去微软实习时,写了一个在麦金塔执行的字串程式库。那是一个很典型的任务:写一个自己的strcat 函数传回指向新字串结尾的指标。只要写几行C就够了。我做的每件事都写在K&R里面(一本讲C程式语言的薄书)。

今天为了要做CityDesk,我必须会Visual Basic、COM、ATL、C++、InnoSetup、Internet Explorer内部机制、正规表示式、DOM、HTML、CSS以及XML。一大堆比古老的K&R更高阶的工具,可是我还是得会K&R讲的东西,否则我就完了。

我们十年前可能想像过,现在会有某些全新的程式设计典范让程式设计更容易。事实上这些年间所建立的抽象机制, 的确 让我们能处理更高复杂度的软体开发(如GUI程式设计和网路程式设计),这是十或十五年前无法处理的。这些伟大的工具(比如OO型式的程式语言)虽然能让我们用飞快的速度完成许多工作,不过总会有一天我们得去追查因抽象渗漏而产生的问题,到时候就得查上两星期了。另外虽然你得雇一个以写VB程式为主的程式师,不过单纯的VB程式师是不够的,因为当VB的抽象机制渗漏时他们就完全卡住了。

抽象渗漏法则正在拖垮我们。

这些网页的内容为表达个人意见。
All contents Copyright © 1999-2006 by Joel Spolsky. All Rights Reserved.