软件随想录:程序员部落酋长Joel谈软件(local.joelonsoftware.com/wiki)-18

为什么微软 Office 的文件格式如此复杂?(以及一些解決方案)

From The Joel on Software Translation Project

Jump to: navigation, search

    上个礼拜,微软公开了他们 Office 软件的二进制文件格式。这些格式看起来真是太疯狂了。Excel 97-2003 的文件格式是一份 349 页的 PDF 档。等一下,这还不是全部喔!这份文件中有下面这句耐人寻味的注解:

 每一份 Excel 工作簿都被存在一份复合文件 (compound file) 中。

    你看,Excel 97-2003 的文件其实是 OLE 复合文件;这意味著:基本上,每个文件中都存在著一份具体而微的文件系统。你得把另外的九份文件都读完才能全盘掌握,够复杂了吧?而且,这些「规格书」的內容看起来比较像是一堆 C 语言的数据结构,而不是我们凭经验所想像的那种规格书。它是一个完整的阶层式文件系统。

    如果你真的开始阅读这些文件,并且幻想著能利用周末的时间写些很炫的程序代码(像是把 Word 文档导入你的部落格中,或是把你的个人帐簿输出成 Excel 文件格式),这些又臭又长的规格书可能很快就会让你心灰意冷。面对这些 Office 文件格式,一个普通的程序员可能会有以下的结论:

  • 一定是故意搞得这么复杂,不想让人看懂的
  • 是一个神经错乱的柏格人搞出来的玩意儿
  • 当初是由一群疯狂的程序设计师制定的
  • 绝对不可能读取、建立这些文件而完全不出错

    以上四点全是错的。让我带你挖掘一些事实,告诉你为什么这些文件格式会复杂到令人难以想像,为什么它们不是「微软的程序写得很烂」的证明,以及你该如何面对这些事实的方法。

    首先我们必须要了解的是:这些二进制文件格式的设计目的和其他的文件格式(像是 HTML)完全不同。

    它们是为了能在老电脑上快速处理而设计的。对早期版本的 Windows 版 Excel 来说,内存的合理使用量是 1 MB,而且在 80386 20MHz 的电脑上应该要能跑得够顺。这些文件格式中有许多设计,是为了能更快速打开、储存文件而做的最佳化:

  • 当有需要的时候,这些文件格式会使用一些非正常的手段,让常用的动作能快些。举例来说:Excel 95 和 Excel 97 有时会利用到一种叫做「简单储存」的功能,可以把文件快速地存成某种变种的 OLE 复合文件格式——因为原本的文件格式在实战中实在是不够快。Word 也有种东西叫做「快速储存」,在储存的时候只把有变动到的地方附加到文件末端,而不是重新写回整个文件。这么做可以让储存加快 14、15 倍。拿以前的硬盘来说,这意味著原本存一个大文件得花 13 秒,现在只要不到一秒就解決了。(这也意味著已经被刪除的资料还是会存在文件之中。最后这成了人们不想要的功能

    这些设计是以既存的函式库为基础的。如果你打算从头开始写一套可以读取二进制格式的程序的话,你得要能支援 Windows Metafile Format(才能显示绘图图型)和 OLE 复合储存。如果你是在 Windows 下开发的话,这些功能都有函式库可以使用,你可以很轻松地完成这些功能.... 使用这些功能是微软团队的捷径。但如果你想要独力完成每一个功能的话,那你就得全部重写一次了。

    Office 大规模地支援复合文件格式。举例来说,你可以把试算表嵌到一份 Word 文件里面。一个合格的 Word 文件处理程序要能够很聪明地处理內嵌的试算表。

    它们不是为了你的脑袋瓜子设计的。这些格式设计的前提(以那个时间点来说是相当合理的)是:Word 的文件格式只需要能够被 Word 读取、写入,就够了。这意味著,每当 Word 开发团队的程序员要去決定怎么修改文件格式的时候,他只需要考虑的事情只有 (a)这样做够快吗? (b)要怎么做才能在现有的 Word 程序代码中做最小幅度的修改就能达成目的。像是 SGML 和 HTML 那种追求「互换性、标准化」的文件格式,在那个 Internet 尚未兴起、文件互换性并不高的年代,并不是设计文件格式时的首要考量——Office 二进制文件格式的诞生时间可比他们早上十年。在那个时候的假设是:如果你需要做文件互换的话,你可以使用「导入/导出」功能。事实上 Word 也真的有一种为了能轻松达成互换的文件格式,称做 RTF。它从一开始就存在了,而且现在的 Word 还是 100% 支援这种格式。

    它们必须反应出应用软件的复杂度。每一个 checkbox、每一个格式选项,以及每一项微软 Office 中的功能,都必须反应在文件格式中的某个角落。那个在 Word 的「段落」选单中,让一个段落在必要的时候能移动到下一页、好让它能和下一段出现在同一页,叫做「与下段同页」的 checkbox?这得出现在文件格式中。而这也就是说,如果你想要实现一个能完美正确地读取 Word 文件的仿制品,你得要实现这个功能。如果你正在写一个足以和 Word 竞争、并且可以读取 Word 文件的文件处理程序,「从文件格式中读取这个设定」的程序代码也许只花你不到几分钟的时间,但你可能得要再花上几个礼拜的时间去调整你的页面规划演算法,好让结果看起来和 Word 一样。否则,你的雇客会用你的程序打开 Word 文件,然后发现所有的页面都乱七八糟。

    它们必须要能反应出应用程序的沿革。这些文件格式如此错综复杂,有很大一部份是为了反应那些老旧的、复杂的、讨人厌的,以及很少人用的功能。为了能向前兼容,它们依然存在于文件格式中——反正留著那些程序代码又不会增加微软什么负担。但如果你真的想要完整透彻地解译、写入这些文件格式,你得要重做一些 15 年前微软的实习生们做过的工作。我们得认清一件事实:最新版的 Word 和 Excel 是数千个开发人年的成果。如果你真的想要完完全全地复制这些应用程序的功能,你一样得花数千人年的工夫。文件格式只是简明扼要地列举了应用程序所支援的所有功能。

    来点有趣的吧!让我们深入一点看个小例子。Excel 的工作表是由一堆不同类型的 BIFF(译注:Binary Interchange File Format)记录组成的。我想要看规格书中的第一个 BIFF 记录的定义。它叫做 1904 记录。

    Excel 的文件格式规格对这个记录的说明很明显地含糊不清。它只提到 1904 记录代表了「是否使用了 1904 日期系统」。唉,又是一个典型的无用规格说明。要是你正在做 Excel 文件相关的程序开发工作,然后你在规格书中发现这个东西,你可能会断定微软又藏了一手。这短短的说明文字没办法给你足够的资讯。你还需要一些额外的知识,我就在这儿说明一下吧。Execl 的工作表可以分为两种:其中一种的日期计算是以 1900 年 1 月 1 日为纪元(包括一个为了和 1-2-3 兼容而存在的闰年错误,不过现在去谈它没啥意思),另一种则是以 1904 年 1 月 1 日为纪元。Excel 两种日期格式都支援,因为第一版的 Excel(Mac 版)直接使用操作系统的纪元系统(这样比较简单);但是 Windows 版的 Excel 必须要能导入使用 1900 纪元系统的 1-2-3 文件。这就足够让你欲哭无泪了。无论是过去还是现在,没有程序员不想依正道而行的;但有时候,你就是得屈服于现实。

    不管是1900还是1904 的文件都很常见,通常是端看那个文件是在 Windows 还是 Mac 上产生出来的。如果我们在使用者不知情的情况下自动转换格式,那么资料很可能会被损毀。因此,Excel 不会自动帮你转换。这不只是个把一个bit 从文件中读取出来的问题。这意味著你得把你的日期显示、处理程序代码整个翻修一遍,好同时支援这两种纪元系统。我想,这大概得花掉你好几天的时间去实现吧。

    没错,当你在写Excel 仿制品的时候,你会发现各式各样处理日期的微妙细节。Excel 在什么情况下会把数字转换成日期?这种格式化是怎么做的?为什么 1/31 会被解译成「1 月 31 日」,而 1/50 又会被解译成「1950 年 1 月 1 日」?这些微妙的动作没办法在文件上三言两语讲清楚;真的要详细地写成文件的话,那文件的內容大概就跟 Excel 的源代码没两样了。

    别忘了这只是几百个 BIFF 记录中的第一个而已,而且还是最简单的一个。大部份的记录都是复杂到会让合格的程序员崩溃的程度。

    唯一可能的结论是:微软释出他们的文件格式对大家是很有帮助的,但这并不代表导入或是储存 Office 文件就会变得比以前简单。这些应用软件的功能极为众多而复杂。你也没办法只实现出 20% 的功能,然后预期 80% 的使用者会满意。这些二进制文件的规格书,顶多只会帮你省下对这套复杂的系统做逆向工程的几分钟罢了。

    好吧!我答应要给你一些可行方案。好消息是:在绝大部份的一般情况下,想要去读些 Office 的二进制文件都是错误的选择。你可以认真地考虑采用另外的两种可行方案:让 Office 自己处理这些问题,或是使用比较容易读写的文件格式。

    让 Office 帮你处理这些繁复的工作。透过 COM Automation 机制,Word 和 Excel 提供了极为完整的对象模型。这个对象模型可以让你用写程序的方式完成任何事情。在许多情况下,你应该要重复利用 Office 內部的功能,而不是试著重新实现一次。以下是一些范例。

  1. 你有一个需要把现有的 Word 文件输出成 PDF 格式的网页介面应用程序。如果是我的话,我会这么做:写几行的 Word VBA 程序,读取一个 Word 文件,再利用 Word 2007 內建的 PDF 转出功能把文件存成 PDF 格式。你可以直接执行这几行程序——即使是从在 IIS 系统下执行的 ASP 或是 ASP.NET 程序中呼叫也没问题。它做得到的。第一次把 Word 唤起可能会花上几秒钟的时间。然后 COM 子系统会把 Word 留在内存中几分钟,以便你再需要它。所以,第二次执行会快得多。这样的速度对于一个网页介面应用程序来说是够快的了。
  2. 需求和前一点相同,但网页服务器得用 Linux。买一台 Windows 2003 服务器,安裝一套完全符合授权的 Word,然后写个小的网页服务程序做这件事。使用 C# 和 ASP.NET,大约是半天的工作量吧。
  3. 需求和前面相同,但规模非常大。丟一台负载平衡服务器在一堆你在第 2 点中建立起来的服务器前面。一行程序代码也不用写。

    这种方法适用于所有你可能服务器上处理的一般 Office 应用。举例来说:

  • 打开一份 Excel 工作簿,排序一些输入格中的资料,重新计算,然后把一些结果放在输出格中。
  • 使用 Excel 产生 GIF 格式的图表。
  • 不需花时间去思考文件格式,把各式各样的资料从任意种类的 Excel 工作表中捉出来。
  • 把 Excel 格式的文件转成 CSV 表格资料(另一种做法是透过 Excel ODBC 驱动程序,利用 SQL 查询把资料吸出来)。
  • 编辑 Word 文件。
  • 填写 Word 格式的表单。
  • 把文件在 Office 支援的各种格式间互转(你可以找到一大票文字处理程序和试算表文件的导入器)。

    在所有的这些情况里,你都有办法让 Office 对象在非互动模式执行,因此它们不会出现在屏幕上,也不会要求使用者输入任何东西。对了,如果你想用这个方法的话,要注意有一些容易发生问题的地方,而且微软对此并没有正式支援,所以在动手之前请先阅读他们提供的这篇 knowledge base 文件

    使用比较简单写入的文件格式。如果你只是想要利用程序产生 Office 文件,你几乎一定能找到比 Office 二进制更好的格式可用,而且可以让 Word 和 Excel 顺利打开,没有任何问题。

  • 如果你只是想要产生给 Excel 使用的表格资料,请考虑使用 CSV。
  • 如果你真的需要 CSV 不支援的试算表计算功能,WK1 格式(Lotus 1-2-3 的文件格式)比 Excel 简单太多了,而且 Excel 也能无误地打开它。
  • 如果你真的、真的必须产生 Excel 原生文件,请试试古早版本的 Excel... Excel 3.0 是个不错的选择。没有什么复合文件的鬼东西,而且只储存你想用的最少功能。使用这样的文件格式,可以将你所需要输出的 BIFF 记录降到最低,然后你只要专心研究规格书的这些部份就好。
  • 至于 Word 文件,请考虑使用 HTML。Word 也能无误地打开。
  • 如果你真的希望在产生出来的 Word 文件里使用很炫的格式,你最好的赌注是产生 RTF 文件。Word 能做到的所有事情,都能用 RTF 表现出来。但 RTF 是以文字格式储存的,不是二进制格式,所以你可以在 RTF 文件中做些变动,然后 Word 依然读得出来。你可以用 Word 产生一份格式很漂亮的文件,在将来要修改的地方放些占位文字,存成 RTF 格式,然后再利用简单的文字替代,即时地把那些占位文字换成想要的文字。这样一来,你就有一份无论哪个版本的 Word 都可以顺利打开的 RTF 文件了。

    反正,除非你打算要写一套和 Office 竞争的软件,并且得要能完美地打开 Office 的文件,否则你大可把这几千个人年的工夫省下来。不管你打算解決什么样的问题,直接去读写 Office 的二进制文件是最浪费人力的一种方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值