作者:周思博 (Joel Spolsky)
February 19, 2008
屬於 Joel on Software, https://www.joelonsoftware.com/2008/02/19/why-are-the-microsoft-office-file-formats-so-complicated-and-some-workarounds/
上個禮拜,微軟公開了他們 Office 軟體的二進位檔案格式。這些格式看起來真是太瘋狂了。Excel 97-2003 的檔案格式是一份 349 頁的 PDF 檔。等一下,這還不是全部喔!這份文件中有下面這句耐人尋味的註解:
每一份 Excel 工作簿都被存在一份複合檔案 (compound file) 中。
你看,Excel 97-2003 的檔案其實是 OLE 複合文件;這意味著:基本上,每個檔案中都存在著一份具體而微的檔案系統。你得把另外的九份文件都讀完才能全盤掌握,夠複雜了吧?而且,這些「規格書」的內容看起來比較像是一堆 C 語言的資料結構,而不是我們憑經驗所想像的那種規格書。它是一個完整的階層式檔案系統。
如果你真的開始閱讀這些文件,並且幻想著能利用週末的時間寫些很炫的程式碼(像是把 Word 檔匯入你的部落格中,或是把你的個人帳簿輸出成 Excel 檔案格式),這些又臭又長的規格書可能很快就會讓你心灰意冷。面對這些 Office 檔案格式,一個普通的程式員可能會有以下的結論:
以上四點全是錯的。讓我帶你挖掘一些事實,告訴你為什麼這些檔案格式會複雜到令人難以想像,為什麼它們不是「微軟的程式寫得很爛」的證明,以及你該如何面對這些事實的方法。
首先我們必須要了解的是:這些二進位檔案格式的設計目的和其他的檔案格式(像是 HTML)是完全不同的。
它們是為了能在老電腦上快速處理而設計的。對早期版本的 Windows 版 Excel 來說,記憶體的合理使用量是 1 MB,而且在 80386 20MHz 的電腦上應該要能跑得夠順。這些檔案格式中有許多設計,是為了能更快速開啟、儲存檔案而做的最佳化:
這些設計是以既存的函式庫為基礎的。如果你打算從頭開始寫一套可以讀取二進位格式的程式的話,你得要能支援 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 內部的功能,而不是試著重新實作一次。以下是一些範例。
這種方法適用於所有你可能伺服器上處理的一般 Office 應用。舉例來說:
在所有的這些情況裡,你都有辦法讓 Office 物件在非互動模式執行,因此它們不會出現在螢幕上,也不會要求使用者輸入任何東西。對了,如果你想用這個方法的話,要注意有一些容易發生問題的地方,而且微軟對此並沒有正式支援,所以在動手之前請先閱讀他們提供的這篇 knowledge base 文件。
使用比較簡單寫入的檔案格式。如果你只是想要利用程式產生 Office 文件,你幾乎一定能找到比 Office 二位進檔更好的格式可用,而且可以讓 Word 和 Excel 順利開啟,沒有任何問題。
反正,除非你打算要寫一套和 Office 競爭的軟體,並且得要能完美地開啟 Office 的檔案,否則你大可把這幾千個人年的工夫省下來。不管你打算解決什麼樣的問題,直接去讀寫 Office 的二進位檔案是最浪費人力的一種方法。
這些網頁的內容為表達個人意見。
All contents Copyright © 1999-2006 by Joel Spolsky. All Rights Reserved.