抽象渗漏法则

作者:周思博 (Joel Spolsky)http://www.joelonsoftware.com

译:Paul May 梅普华 sink.cup

原作发表于:2002年11月11日

ChineseTraditional:[2]

你每天不可或缺的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试图提供一个完整的抽象机制,想隐藏底下不可靠的网络,不过有时候网络会渗漏越过抽象机制,这时就会觉得抽象其实并不太能真的提供保护。这只是我所谓「抽象渗漏法则」的一个例子而已:

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

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

像扫描一个大的二维矩阵这么简单的动作,是由水平方向或垂直方向扫描都会严重影响效率,影响的大小依「木纹」(译注:二维阵列排列的方式)的方向而定,某个方向可能比另一个方向多产生许多的分页失败,而分页失败是很慢的。虽然写组合语言的程序员应该可以假设自己拥有可连续定址的内存空间,不过虚拟内存表示这种假设只是种抽象机制而已。当出现分页失败时或是某些内存读取时漏洞就会出现,处理时间会比其他内存慢几毫微秒。

SQL语言希望把数据库查询的程序抽象化,让你只要定义想要的东西,查询动作的细节就交由数据库去处理。不过在某些状况下,有些 SQL查询比逻辑上相等的查询慢上几千倍。这有个很有名的例子,在某个SQL服务器用"where a=b and b=c and a=c"来查询,会比用"where a=b and b=c"快上许多,可是查询的结果其实是一样的。照道理只要指定规格,并不需要在意程序。可是有时候抽象机制会失效并导致很差的效率,于是你就得跳出来用查询规划分析器找出问题,然后想办法加快查询。

NFS或SMB之类的网络程序库,能让你「像」处理本机文件一样地处理远程机器的文件。有时候网速会变得很慢或是断线,这时远程文件就不再像是在本机上了,而身为程序员的你必须加程序代码来处理这种状况。「远程文件和本地文件一样」的抽象机制出现漏洞了。 这里有个Unix系统管理员的具体例子。如果你把使用者的home目录放在用NFS挂入的硬盘上(一种抽象机制),而使用者建了一个.forward文件把他们的电邮全部转发到其他地方(另一种抽象机制),如果新邮件进来时NFS服务器停掉了,由于找不到.forward档信息并不会被转寄出去。这个抽象机制的漏洞就真的会把一些信息丢掉。

C++字符串类应该能让你假装字符串是个第一级(first-class)资料。它们尝试把「字符串很难处理」这个事实抽象掉,让它使用上像整数一样容易。几乎所有C++字符串类别都会重载+运算符,才能把字符串连接写成s + “bar”。不过你知道吗?不过怎么努力,世上还是没有C++字符串类能让你写成"foo" + “bar”,因为C++里的字符串常数一定是char*,绝对不会变成字符串。这个抽象机制呈现一个程序语言本身不给补的漏洞。(有趣的是,C++随时间演进的历史,可以描述成尝试用修补字符串抽象机制漏洞的过程。他们为什么不直接在语言本身加个原生的字符串类别?这实在让我搞不懂。)

再来就是下雨天时开车没办法开得和平常一样快,虽然车上有挡风玻璃雨刷有头灯有车顶还有暖气,这些装备应该是让你可以忽略下雨这个事实 (他们把天气抽象化了),不过看吧,你还是得担心天雨路滑,有时候雨甚至会大到你看不远,所以在只好慢慢地开,因为基于抽象渗漏法则,天气永远不能完全被抽象化。

抽象渗漏法则会造成问题的原因之一,是因为它说明了抽象机制并不真能照原构想简化我们的生活。当我想训练某人成为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的抽象机制渗漏时他们就完全卡住了。

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

这些网页的内容为表达个人意见。