# 二十六、漏洞抽象定律
你每天不可或缺的Internet里有個關鍵的小魔法,這個魔法就在TCP通訊協議這 個internet的基礎協定里。
TCP是一種可靠的數據傳輸方法。我說可靠是指如果用TCP在網絡上傳一個訊息, 訊息一定會到,絕不會亂掉或壞掉。
TCP的用途很多,比如抓取網頁數據或傳電子郵件都是。由于TCP這么可靠,連那 些挪用錢的東非人電郵(譯注:指有陣子常見到的騙人信)都能完整無缺的到達, 真是好笑。
相對的有另一種叫IP的不_數據傳輸方法。丨P不保證數據會傳到,就算到了數 據也可能會亂掉。如果你用IP傳送一堆訊息,很可能只有一半的訊息到達,而 且其中還有一些到達的順序和原先傳送時的順序不同,另外可能有幾個訊息的內 容會變掉,可能變成可愛的猩猩寶貝照片,更可能變成一堆看不懂的垃圾,看 起來就像臺灣垃圾信的標題一樣。
這里就是魔法所在:TCP是架在IP上面的。換句話說,TCP不得不靠一個不可靠游 工為想辦法可靠地傳送數據。
為了說明這的確是個魔法,想想下面這個本質上相同(雖然有點滑稽),來自真實 世界的情節。
想象你有個方法把演員由百老匯送到好萊塢,基本上就是讓人坐上車后開車橫越 國家送過去。有些車會出車禍讓可憐的演員掛掉。有時候演員在路上喝醉了就 去剃光頭或刺納粹刺青,結果變得太丑而不能在好萊塢工作。另外由于走的路線 不同,演員到達的順序常會跟出發的順序不一樣。現在想象有個叫好萊塢快遞 的新服務,可以把演員送到好萊塢,并且保證演員一定會(a)到達,并保證(b) 順序不變而且(c)狀態完美地到達。神奇之處在于好萊塢快遞除了原本的車子以 外,并沒有新的運送方法。好萊塢快遞的作法是在每個演員抵達時檢查演員的狀 況,如果狀況不佳就打電話請公司把該演員的雙胞胎送來。如果演員到達的順序 不對,好萊塢快遞會照正確順序重新排好。如果51區有架大幽浮在內華達的高速 公路上墜毀阻斷了交通,預定走這條路線的演員就會改走亞歷桑那州,好萊塢快 遞甚至不會把事情告訴加州的導演。導演只會覺得演員來得比平常慢,他們甚 至不會所至幽浮失事的消息。
TCP的魔法大致上就是這樣。這種作法常被計算機科學家稱為猶康:把復雜許多 的東西隱藏起來的一種簡化動作。結果很多計算機程序的設計都是在建立抽象 機制。字符串鏈接庫是什么?它是一種偽裝,假裝計算機能像處理數字一樣輕易 的處理字符串。文件系統又是什么?也是一種偽裝,假裝硬盤并不是一堆不停 旋轉,可以儲存位的磁性盤片,而是一個有著層層目錄的階層式系統,可以存放 一個個由一或多個字節字符串構成的檔案。
把話題拉回TCP。稍早為了讓事情單純一點,我撒了一個小謊,而且現在有些人
可能會因為這個謊氣得頭上冒煙。我說過TCP保證你的訊息會到達,其實并不會。 如果你養的蛇把連接計算機的網絡線咬斷了,就沒存任何IP封包可以通過,這時 候TCP當然也不可能讓你的訊息抵達。如果你惹毛了公司的系統管理員,他們為 了報復就把你接到已經超過負荷的集線器,因此只有部份的丨P封包能通過,這時 候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之類的網絡鏈接庫,能讓你「像」處理本機檔案一樣地處 理遠程機器的檔案。有時候連接速度會變得很慢或是斷線,這時遠 程檔案就不再像是在本機上了,而身為程序員的你必須加程序代碼 來處理這種狀況。「遠程檔案和本地檔案一樣」的抽象機制出現漏 迥了。這里有個Unix系統管理員的具體例子。如果你把用戶的home 目錄放在用NFS掛入的磁盤上(一種抽象機制),而使用者建了一 個.forward檔案把他們的電郵全部轉寄到其他地方(另一種抽象 機制),如果新郵件進來時NFS服務器停掉了,由于找不到.forward 文件訊息并不會被轉寄出去。這個抽象機制的漏洞就真的會把一 些訊息丟掉。
4. C++字符串類別應該能讓你假裝字符串是個第一級(first-class) 資料。它們嘗試把「字符串很難處理」這個事實抽象掉,讓它使用 上像整數一樣容易。幾乎所有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的精靈和各個程序 產生功能。不過萬一出了任何問題,他們根本不會知道怎么回事,也不知道如何 除錯或回復。我還是得教他們I Unknown和CLSI D還有ProgIDS以及。哦,饒了我吧!
在教ASP.NET程序設計時,最好只要教學生可以在組件上雙擊,然后就能撰寫用 戶點擊該組件時在服務器執行的程序。不過處理超鏈接(`<a>`)點擊事件的HTML 程序,和某個按鈕被按時的處理程序是不一樣的,而ASP.NET實際上是把這之間 的差異抽象化了。問題來了,ASP.NET的設計者必須把HTML無法由超鏈接傳送表 格的事實隱藏起來。他們的做法是在超鏈接的onclick產理加上幾行JavaScript 程序。不過這種抽象機制也有漏洞,如果用戶關閉JavaScript功能,ASP.NET 的應用程序就不能正常的運作了,萬一程序員又不了解ASP.NET抽象掉什么東西, 根本不可能知道出了什么問題。
抽象滲漏法則表示,當某人發明一套神奇的新程序產生工具,可以大幅提升效率 等等,就會聽到很多人說:「應該先學會如何手動進行,然后才用這個神奇的 工具來節省時間。」程序產生工具假裝抽象掉某些東西,和其他所有抽象機制 一樣都有漏洞,而唯一能適當處理漏洞的方法,就是弄懂該抽象原理以及所隱藏 的東西。所以抽象機制雖然替我們節省了工作的時間,不過學習的時間是省不 掉的。
而這一切都似非而是地表示,即使我們擁有愈來愈高階的程序設計工具,抽象化 也做得愈來愈好,要成為一個純熟的程序員卻是愈來愈難了。
我第一次去微軟實習時,寫了一個在麥金塔執行的字符串鏈接庫。那是一個很典 型的任務:寫一個自己的st「cat函數傳回指向新字符串結尾的指針。只要寫幾行 C就夠了。我做的每件事都寫在K&R里面(一本講C程序語言的薄書)。
今天為了要做CityDesk,我必須會Visual Basic、COM、ATL、C++、InnoSetup、Internet Explorer內部機制、正規表不式、DOM、HTML、CSS以及XML。一大堆比古老的K&R更高階的工具,可是我還是得會K&R講的東西,否則我就完了。
我們十年前可能想象過,現在會有某些全新的程序設計典范讓程序設計更容易。 事實上這些年間所建立的抽象機制,游磁讓我們能處理更高復雜度的軟件開發 (如GUI程序設計和網絡程序設計),這是十或十五年前無法處理的。這些偉大的 工具(比如00型式的程序語言)雖然能讓我們用飛快的速度完成許多工作,不過 總會有一天我們得去追查因抽象滲漏而產生的問題,到時候就得查上兩星期了。 另外雖然你得雇一個以寫VB程序為主的程序員,不過單純的VB程序員是不夠的, 因為當VB的抽象機制滲漏時他們就完全卡住了。
抽象滲漏法則正在拖垮我們。
- 第一部分 位與字節:編程實踐點滴
- 一、語言的選擇
- 二、深入底層
- 三、joel測試:改進代碼的12個步驟
- 四、每一位軟件開發人員必須、絕對要至少具備UNICODE 與字符集知識(沒有任何例外!)
- 五、輕松寫就功能規格說明書 - 第1節:為什么煩心?
- 六、輕松寫就功能規格說明書 - 第2節:什么是規格說明書?
- 七、輕松寫就功能規格說明書 - 第3節:但是……如何?
- 八、輕松寫就功能規格說明書 - 第4節:技巧
- 九、輕松制訂軟件進度表
- 十、每日連編是朋友
- 十一、難伺候的故障修復
- 十二、軟件開發中的5個世界
- 十三、稿紙原型開發
- 十四、不要被太空架構師所嚇倒
- 十五、開火與運動
- 十六、人員技能
- 十七、源于計算機學科的三個錯誤思想
- 十八、二元文化
- 十九、自動獲取用戶故障報表
- 第二部分 開發人員的管理
- 二十、面試游擊指南
- 二十一、重金激勵害多利少
- 二十、二不配備測試人員的五個首要(錯誤)原因
- 二十三、任務換人有害無益
- 二十四、絕不去做的事情,第一部
- 二十五、冰川下的秘密
- 二十六、漏洞抽象定律
- 二十七、程序設計界的LordPalmerston
- 二十八、評測
- 第三部分 Joel對常態問題的遐想
- 二十九、RickChapman解讀愚昧
- 三十、在這個國家狗是干什么的? 我們有多么天真?
- 三十一、作為哼哈二將,只管去做事
- 三十二、兩個故事
- 三十三、巨無霸麥當勞與天才廚師JamieOliver
- 三十四、沒有什么像IT看起來那么簡單
- 三十五、提防非自主開發綜合癥
- 三十六、策略I:BEN&JERRY公司與AMAZON
- 三十七、策略II:雞與蛋問題
- 三十八、策略III:讓我回去!
- 三十九、策略IV:大件與80/20神話
- 四十、策略V:公開源代碼的經濟因素
- 四十一、墨菲法則肆掠的禮拜
- 四十二、微軟公司是如何敗北API之戰的
- 第四部分 對.NET稍多的評說
- 四十三、微軟精神失常了
- 四十四、我們的.NET對策
- 四十五、請問,我可以使用連接程序嗎