2010/10/17

翻譯:皮諾丘難題(The Pinocchio Problem)by Steve Yegge

文章:The Pinocchio Problem 皮諾丘難題
日期:2007.01.17
作者:Steve Yegge
作者的部落格(2006至今):Stevey's Blog Rants
作者舊的文章(2004與2005):Stevey's Drunken Blog Rants

作者簡介:
目前任職於Google,之前任職於Geoworks與Amazon。程式語言生涯中有過兩次非常關鍵性的轉換,一次是組合語言,一次是Java 跟Perl,因為發現解決語言本身設計帶來的問題所花的時間,竟然比真正用在開發軟體系統的時間還多。其文章以長度聞名,長到應該稱為論文而非部落格,兼帶詼諧筆風,發表頻率大約一個月一到兩篇,作者總是說這些是在凌晨三罐啤酒下肚後的誇誇其談,但每一篇都是經過長時間醞釀,內容充實有見地的傑作。
獨立以Java/JPyton開發多人線上遊戲Wyvern,可讓玩家自行創建擴充遊戲內容。其工作團隊將Rails移植到Rhino上,Rhino是運作於JVM平台上的JavaScript引擎,Rails(Ruby on Rails)為一套受到廣泛喜愛使用的網站開發模組;為Emacs撰寫完整的JavaScript環境,期望在Emacs上可以有一套 JavaScript IDE,以及將來可用JavaScript而非elisp來開發Emacs extensions。


皮諾丘難題(The Pinocchio Problem)


過去一年來,我將自己每月花在寫部落格的時間限定在四小時左右,通常我會有很多主題可以寫、很多東西可以討論,選一個出來並不會太難,但上個月在我腦袋裡只有一件事在打轉,我不斷在嘗試著用各種方式把它說出來,都快抓狂了──你知道的,用一種又好又簡潔的方式表達出來,讓讀者能夠把它嚥下去,可惜到目前為止都沒成功,我想那代表我還不真的懂這主題,或許試著寫下來可以幫我更了解。

這主題是關於軟體設計;你瞧,似乎有著某種好方法可以用來設計軟體,甚至有人認為存在著一種最好的方法,但沒人實際運用啊,嗯,或許一小撮人有,即使如此,我認為那些少數例子有一半僅是碰巧吧了。

斷斷續續地,這個題目我已經思考研究了很長一段時間,十八年的光陰過去了,而我依然不能清楚明確地說出……嗯,設計的原則,如果這是我們要的答案的話;但上個月,我覺得我好像又更靠近了一點,或許你可以幫我一下!我會告訴你我知道的,那你可以告訴我你知道的,或許我們可以合作出某種非凡的結果。

先為今天這篇設定一些背景,讓我用一個句子將所有我寫過的文章內容做總結:我認為大部分的軟體都是大便。嗯,好像不夠正確,更誠實地說,我認為所有軟體都是大便;耶,沒錯,你聽到了,Stevey一言以蔽之:軟體皆大便。

即使如此,我認為某些軟體系統確實比其他的好:某些大便生產者可能有吃過一些金幣,所以他們的大便看起來比較亮晶晶一點,偶爾有人吞下紅寶石,他們的大便就又好看又值錢,嗯,記得站遠一點看。但是,不論吃進了多少寶貴的石頭,大出米田共就還是米田共。


多麼可愛有趣的譬喻啊,對吧?我自己想的喔!

每想到我最喜歡的軟體系統時──像熟悉的老朋友們、像適合居住的福地──我看到它們都擁有某些特質,很快我就會告訴你是什麼特質,至少是那些我注意到的,別急,我保證不拖戲,但事情還沒結束;一旦你將這些特質加進一個軟體系統中,如果做的對的話,那麼通常你可以得到一套跟時下做得出來的好軟體一樣好。不過,仍只能算是大便而已。

在我開始思考若能脫離屎的層次來打造軟體系統時,真正的難題就開始浮現,這樣的思索引發出各種有趣的問題,而我一點頭緒也沒有,但我會把這些問題也丟出來,只要我還沒超過四小時的時間限制,還沒被生活瑣事招喚過去的話。

我喜愛的系統

在上個月左右的某個時刻我領悟到一個重點,那就是,我喜歡的軟體系統所共同擁有的那些特質都是由一個根本原因所衍生出來的:某單一特質、或稱為設計原則,軟體如果擁有這特質的話會自然而然地呈現出正確的特性。

哪些是我喜愛的軟體系統呢?以下列出最好的幾個:Unix、Windows XP、Mac OS/X、Emacs、Microsoft Excel、Firefox、Ruby on Rails、Python、Ruby、Scheme、Common Lisp、LP Muds、Java Virtual Machine。

有幾個差一點點的:Microsoft Word、OmniGraffle Pro、JavaScript、Perforce。

還有幾個應該可以排上去,但我因不夠熟悉而不敢亂說的系統:GIMP、Mathematica、VIM、Lua、Internet Explorer。

流行的軟體系統大部分都挨不上名次,雖然它們都相當有用,但是我認為缺少了理想軟體設計的本質,例如:IntelliJ、Eclipse、Visual Studio、Java、C++、Perl、Nethack、Microsoft PowerPoint,所有任天堂與PlayStation的遊戲,幾乎所有的電腦遊戲,除了著名的毀滅戰士(Doom)與雷神之鎚(Quake),還有大部分的網站服務,包括那些很有用的Amazon.com或是Google Maps。

我就不賣關子了,我認為在所有的軟體設計理念之中,最最重要的原則是:系統毋須重新啟動

如果你設計一套系統可以不需要重新啟動,那麼,你終將會,就算是以很迂迴的方式,終將實現出一套可永久存活的系統。

我上面列出的系統,仍需要偶爾重新啟動一下,也就是說,它們實際上的壽命會落在數十年到一百多年之間,其中有幾個才剛開始──大部分都在二十出頭跟三十出頭左右,少數幾個已經有四十年的資歷了,然而,它們還是離不朽不死的境界很遠很遠。

我心中第二最最重要的原則,實際上算是第一原則的推論,是一套系統必須能成長而毋須重新啟動;一套不能隨時間成長的系統是靜止的,其實不能算是一套系統,應該說是一支函式,它可能是支非常複雜的函式,可以接受很多不同的輸入與產生輸出,它可能是支非常有用的函式而且存活相當久的時間,但函式一定會被取代掉,或是被可成長的系統吸收掉;我現在相信,經過了將近二十年的思考研究,能夠成長改變而毋須重新啟動的系統可以永久地存活下去。

必備特質

下面列出目前世界上最優質軟體共有的幾點特性,我列出來的它們並非全都都有,我想只有少數幾套系統擁有我列出來的全部特點,你應該會發現,一套系統有的必要特質越多,就會越強大、更重要、活的更久。

注意:這些特點都是給程式設計師看的,芸芸使用者們在乎的功能跟一套系統的壽命無關;我相信適當的時刻一到,程式設計能力就會會變得跟識字能力一樣普遍,所以這不重要。

第一:偉大的軟體系統都有命令列模式(command shell),這是系統不可或缺的部份,與系統同生同存,系統設計者無法想像沒了命令列模式的日子要怎麼過啊,命令列模式是完整的系統操控介面:任何你以其他方式操作系統完成的動作都可以透過命令列模式達成,光搞一套優秀的命令列模式本身就是一個大主題(大部分的必備特質都是這樣的,想想吧),我概略說一下:一套優秀的命令列模式一定會包含下命令的語言(command language)、互動式的查詢工具、腳本語言(scripting language)、擴充機制、命令歷史紀錄機能、以及命令列編輯器(command-line editor),一套超棒的命令列模式就是一套好系統的最佳典範:可以比使用它的母系統存活更久並可以移棲到別套系統中。

所有現存的命令列模式都是大便,但它們是今日可以被打造出來的好軟體不可或缺的元件之一。

我們可以把Emacs想像成一套非常厲害的終極命令列模式:想想如果把命令列模式推到理論極限會是什麼樣的光景,或至少推到現今人們可以構思出來的境界。

最佳軟體系統共有的必備特質還有很多,命令列模式不是唯一一個,所以,讓我們繼續談下去。

偉大的系統還要包含“advice”,這項特質並沒有一個大家都同意的名稱,有時稱為hooks(掛勾)、或filters(篩檢器)、或aspect-oriented programming(剖面導向程式設計、面向側面的程式設計、觀點導向編程;譯註:這怎麼翻啊?);就我所知,Lisp最早有這項特質,並以advice稱之;所謂的advice是一套小小的開發框架,在系統執行某動作或函式時,讓你以置入程式碼的方式,在動作之前、之中、之後插入你想要的功能,藉以改變系統的行為;每套advice系統都不一樣,它能夠介入的範圍越廣──意思是,它可以修改系統行為的範圍越大──那麼使用它的母系統功能就越強大。

Ruby on Rails有一套小小的advice系統,稱為“filters”,它(只)可以附加行為到controller actions中,所謂controllers actions是處理描繪頁面要求的特殊函式,你沒辦法在系統中的其他函式加上“之前、之後、之中”的filter,不過透過Ruby的metaprogramming機制可以在某種程度上做到;但一套advice系統不能僅是紙上談兵理論上沒問題,那是不夠的,advice必須從頭從底層融合進系統之中,以first-class形式、以有詳細文件說明的介面開放出來;advice非常強大有用,即使是Rails中簡單的action-filtering系統,都釋放出驚人的彈性;想想沒有它的話,要怎麼寫支Rails程式啊。

Emacs有套非常精密的advice系統;Common Lisp有一套可以說是世上最強大的advice系統,這支語言這麼強有很大的因素就是因為它;Aspect-Oriented Programming企圖在Java語言放進一套advice系統,野心很大,但因為Java的先天限制,必須以附加在語言外的擴充機制出現,有自己的編譯器與其他工具,這麼一來就大大阻礙了大家採用的意願,另外一個障礙物是,Java程序員偏愛撰寫死寂的軟體系統,任何能讓系統有生命的呼吸跡象都讓他們感到頭痛不已。

我要承認我也感到頭動,有生命的軟體聽起來有點嚇人,不意外,大部分的程式設計師都偏好開發牽線木偶而非真實的活人,掌控牽線木偶容易多了,但我認為有生命的軟體更加有趣也更有用些,坦白說這是無論如何都不能避免的趨勢,所以我們最好努力了解,對我而言,那就代表著實際動手建造一個出來。

讓我們繼續往下說,世界級的軟體系統都有套擴充語言(extension language)外掛系統(plug-in system)──讓開發者可以延伸系統的基礎機能,有時候外掛(plug-in)又稱為“mods”,這是一種機制,讓使用者們以設計者不須參與的方式來擴充延伸系統的能力。

Microsoft Excel有一套絕佳的mod系統,這是一套令人激賞的開發框架,幾乎就等於是一套平台了,如同其他優質的mod系統一樣,它是一層一層的,從簡單Excel macros(Excel巨集),一直到完整的COM介面,可以被VB甚至是Python或Ruby驅動,只要那語言有COM bindings的話。


讓毀滅戰士(Doom)與雷神之鎚(Quake)這麼受歡迎的必殺特質之一就是它們的mod系統,毀滅戰士有一些基本的機制,以腳本驅動你建造的關卡、敵人、甚至是遊戲運行邏輯;雷神之鎚有QuakeC,仍然是,我認為啦,電腦遊戲腳本驅動語言的優良標準,不過我的資訊有點過時了,因為過去五年多來我都在玩電視遊樂器,很悲哀地,那些遊戲都死的不能再死了。

最強的外掛系統,強到能夠把整支應用軟體以plug-in的形式開發出來,這就是Emacs跟Eclipse的核心理念;先有一層很小的啟動層,可以看做是整個系統的硬體,系統其餘的部分,其所涵蓋的範圍(通常被效能因素限制住),都是由擴充語言寫出來的。

Firefox有套外掛系統,這真的是一套大便,但有總比沒有好;如果你有一套外掛系統,你很快就會發現,一定會有一些發狂的開發人員學習使用它並且榨乾它的能力直到極限,這會讓你誤以為你建構出來的外掛系統還不錯,但它應該要有容易使用跟毋須重起系統這兩項特性;Firefox打破了這兩項重要的規則,所以它的壽命處於一種不穩定的狀態下:要不然修正這兩項,要不然更好的系統會出現把大家都吸引過去。

真的讓我吃驚的是,連Firefox extension的開發人員都沒有特殊的管道跳過重新啟動這條災厄之路,他們的開發流程很痛苦,每有更動就必須手動重新安裝外掛(使用GUI操作方式),藉由某些技巧可以避掉大約一半的步驟,但整個開發社群對這點真的在皺眉表示不滿了!他們很明顯地感受到,如果使用者要經驗安裝外掛的痛苦一次,那外掛開發人員每當改一行程式碼就必須痛一次,每一次都在提醒他們:Firefox的外掛系統是沱大便,根本不能做事嘛。嘿嘿,真是有趣!

有位開發人員名叫Aaron Boodman送給了Firefox一塊瑰寶,這人現在在Google,那瑰寶就是GreaseMonkey,提供了另一種方式來寫Firefox extensions,不像Firefox的外掛系統,GreaseMonkey的extensions可以被安裝且更新,不需要重啟系統,而且相當容易撰寫,這瑰寶替Firefox注入了新動力,我打賭大部分的Firefox開發人員(先不管使用者社群)都能了解並感激GreaseMonkey的重要性,因為它讓Firefox能長期存活下去。

有趣的是,GreaseMonkey本身是個Firefox外掛,但又提供它自己的外掛系統,這是外掛系統中常見的一個模式:有些外掛會成長變大,變得可組態化,最後可被視為獨立的程式系統;Emacs有很多這種外掛:advice套件就是一個好例子。順帶一提,Rails的filter系統也是以系統外掛的形式實作出來的。外掛(plug-ins),跟其他軟體系統一樣,也有生命週期,視乎它們納進多少我今天在談論的特質,它們最終都需要命令列模式、advice、擴充語言、等等,然而,外掛因為是立基於另一套系統之上,通常一開始就有這些東西。

打造可延伸擴充的系統比不能擴充的系統要更加困難,一般說來大約是三到五倍;在一開始就加入外掛系統比較容易,想在一套現存的系統上加進擴充能力,那可是夭壽困難的功夫啊,而且需要大規模地進行系統重構(refacotring),可不是Java圈圈流行的那種小小可自動化的重構,其需要花費的心力幾乎就跟重寫整套系統差不多,不過以重構的方式進行的話,可以靠縝密完善的unit tests來減低改錯的風險。

現今有很多軟體系統都只能透過網路遠端存取,例如經由瀏覽器與HTTP,一些建造這種系統的大公司們,包括Yahoo!、Amazon.com跟eBay,都開始意識到提供給程序員的擴充延展能力是他們系統永續存活的關鍵,他們開始釋出內部系統的部分介面給外界使用,通常以web service的方式,這提供了某程度的擴充性:讓外界獨立軟體開發人員可以打造特別的介面來存取系統;隨著時間演進,我認為在網路服務這塊領域分高下的主要分水嶺將取決於供程序員存取系統的介面的品質高低。

外掛系統要考慮到安全性,要考慮到易用性,要考慮命名空間,要考慮元件相依性,要考慮向後相容的問題,要搞一套就已經是該死的難了,更別提要搞的好;外掛絕對是軟體系統想要永續存活所需要的特質。

優良的軟體系統還需要哪些特性呢?

我認為有點很重要,至少在今天來說,偉大的軟體系統都有或是需要一支殺手級應用程式,每套世界級的軟體系統本質上就是一個平台,如果你有命令列模式、擴充語言跟advice以及外掛架構,那麼你有的就是平台的組成要素了,但你需要某理由來說服使用者們使用你的平台;所以,GIMP打出影像編輯這賣點,Eclipse是撰寫Java程式碼,Emacs是一般文字的編輯器,Rails是建造網站服務,Firefox是瀏覽網頁用的,Python是腳本語言用來驅動其他東西,Lua可用在嵌入式環境,Mathematica是數學運算,等等,它們也都可以被延展應用到其他領域,但它們在其利基領域中是最強的。

最泛用的軟體系統,我認為,是作業系統跟程式語言,即使如此,它們也都有個主要的領域,作業系統主要用於資源管理,大部分成功的程式語言通常都切割出一塊利基市場加以佔據:需要速度就用C++,做Unix系統管理時可用Perl,要用很肥大的API來嚇嚇客戶的話可以用Java,瀏覽器上幾乎都只能選用JavaScript,在Emacs上就只能用Lisp,還有,Ruby開始爆紅要歸功於Rails。

所以軟體系統要有個利基點。或許將來有一天,有套超級泛用的軟體系統,超級強大、設計超良好,以至於任何事情都適合用它來完成;或許會有那麼一天吧,或許你就是打造這套系統的人。

接下來是今天我要講的重要特質的最後一項,跟其他項一樣重要,就是優良的軟體系統是能夠自我檢視有內省能力(introspective),你可以在執行時到處戳戳看檢視系統的狀態,理想情況是由系統自己戳自己、自己檢視自己,至少要做基本的狀態監視,即使是一個已經有一堆靜態檢查的小小系統,你仍然需要監控,例如輸入與輸出佇列,對大系統來說,你什麼東西都需要做監控,包括監控系統本身。(若是不做這種監控系統的監控,可能會發生一件糟糕的事情,你的系統進入某種狀態,所有狀態檢查都OK,但系統實際上已經在擺爛了。)

內省機制(introspection) 可以(且應該)有各種不同的形式,不僅是狀態監控,系統管理工具與診斷工具也是一種內省,一步步除錯追蹤也是,效能分析也是,動態連結也是:系統必須能夠(舉例而言)執行bytecode verifiers以確保剛載入的程式碼能通過一些基本的安全性檢查。內省機制通常會降低效能,所以很多程序員不會在執行時做任何的內省檢測,或是極簡化地採用底層系統提供的內省機制(例,RTTI或Java的reflection),如果你用內省機制來換取速度,等於折掉你軟體數年甚至數十年的壽命,如果你是個顧問公司,或是你只想要讓系統可運作後就不管了,那麼或許這點不是太重要,但我認為大部分寫軟體人的都偏好在能長久永存的系統上做開發。

優質軟體共有的特質還有一長串可寫,但行文至此,我想最好談一下為什麼這些特質都根源於“毋須重新啟動”,之後遇到其他特質你就可以輕易地辨識出。

重新啟動等於死亡

今天我沒辦法很公平地談這個主題,部分原因是這個題目很大,部分原因是我還沒通徹搞懂,所以我最多只能把輪廓描繪出來,希望你能看出這問題完整的面貌。

首先,讓我們從比較哲學的觀點來看看,我用一種很廣義的觀點來看待軟體:大部分的軟體都不是人類創造的,那些人造的都是些大便,有很多相當棒的自然存在的軟體,例如人類大腦的運作,其大部分可以被看做是軟體,還有我們身體的組成運作很明確地是軟體。(見鬼了,我們甚至可以在DNA中看見組合語言指令碼,雖然還沒被完全解碼。)所以人們至少帶著兩套軟體系統:肉體與心靈。

我也認為穩定的生態系統也算是軟體系統,喔,順帶一提,穩定的政府也算,還有,人群組成的組織也是,例如你的公司;除非我整個誤解涂林的觀點,要不然軟體可以是任何能做運算的東西,而計算只需要一台有著幾個簡單機制的機器,這些機制是用來改變機器的狀態,包括條件式分支、跳躍指令、讀寫狀態、加上一組指令(程式)來指示該做哪些動作。

我假設我在這給軟體下的定義,是相當清楚且不證自明的, 所以,關於軟體並非皆人造這觀點,我就不用再多做解釋辯護了。

所以,我的第一個反對重新啟動的論點是,在自然界中可沒這回事,或更精準地說,真的發生的話後果都很悲慘;如果你不喜歡一個人的所作所為,你不能殺掉他們、修正DNA、然後從小孩再成長一次;如果你不喜歡政府,你不能說關就關、找出錯在哪、然後重新開始,那麼,為什麼我們幾乎都用這種方式開發軟體呢?

反對重新啟動的下一個論點,看看現在開發人員建造的過時舊軟體,把你寫程式時戴的眼鏡拿下來,以一般使用者的角度想想重新啟動這回事,你喜歡被迫使重新啟動軟體嗎?當然不囉,多不方便啊。

大約十年前,微軟下了個決定要把Windows NT中需要重開機的地方都修改掉,你還記得嗎?我從未在微軟工作過,也是輾轉得知這故事,所以我不是確切了解事情始末,聽說那時每個小小的組態改變都需要系統重開機,好像是某個高層忍不下去了,然後他就組織統整了很多人力來修改程式,當我聽到這軼聞時,已經減少到大約剩下五個地方需要重開機;想想這對美國財政的衝擊吧──數以百萬的人們每天省下5到30分鐘的時間等待Windows重開機,很巨大的影響對吧,如果他們能夠將藍天白雲死機畫面也改好,那這就是一套相當棒的軟體,對吧?

Linux在這方面也經歷了不少成長的苦痛,終於開始越來越好,但我猜今時今日它需要重開機的次數仍然比Windows還要多。

不論如何,重新啟動這問題不僅僅是帶來不便而已,應該還有更深的意涵,從一個比較激進但仍可加以辯護的觀點看來,一次重新啟動就等於一件謀殺案。

怎麼會呢?嗯,古今中外尚未解決的難題之一是關於意識(consciousness)的問題:這是個啥玩意?有意識代表著什麼?到底什麼叫做自我察覺(self-aware)?寵物有意識嗎?昆蟲呢?捕蠅草呢?分界線要畫在哪裡呢?

歷史上各個時代,關於意識的問題有時流行有時不流行,不過在過去數十年間這是個非常風行的話題,哲學家跟認知科學家正攜手合作試著找出解答,據我所知,主流的觀點(或是叫領先的理論,什麼都好啦)是這麼說的:意識是遞迴式的自我察覺與認知,而且是漸層式的(對比於簡單的開與關),很明顯的是一種的函數,複雜度視乎可以處理多少輸入以及大腦可以同時處理的自我察覺之遞迴階層的多寡而定。所以,寵物是有意識的,只不過不像人類那樣。至少這是我從過去三十多年所出版的書籍、研究報告和論文,所得到的結論。

舉個例,我過去一直把我的狗──奇諾(一隻獅子犬)──當做一台非常簡單的狀態機:噓噓、拉屎、睡、吃、玩、花數小時舔鼻子,但每當我更進一步了解他,就逐漸辨識出更多的狀態,有數百個之多,甚至數千個,但我依然尚未完全被說服他的行為不是決定性的(deterministic),我愛他愛到每一根毛我都愛,所以就假設他有懷疑的能力以及有自由意志(free will)吧,但不用太費心就可以有90%的準確率猜中他的行為;我想像這套用在我身上也同樣成立吧!

狗似乎比老鼠複雜多了,而老鼠應該比蟑螂複雜許多,一直循線追下去(或許是螞蟻?扁形蟲?單細胞有機體?),看起來到那時,動物的行為應該是具有完全可決定性的。

起初那說法對我很合理,但我現在的想法改了,我個人認為所有的生物都具有某些不可決定性(nondeterminism),其程度視乎他們的組成因素中軟體的範圍有多廣,硬體部分肯定是可決定性的,奇諾在出生後任何不用教就會做的事情不是硬體就是軔體(一種內建的軟體,無法移除或更改),進行尿尿的程序(以及何時該去)、打噴嚏跟其他反射動作、處理感官資訊、消化食物、以及其他上千個程序通通都是直接編碼在他的大腦軔體或身體軔體中,可被精準地預測其行為,任何觀察到的差異處都是因為軟體部份對當前環境做出的修正。

換句話說,我認為意識與自由意志(也就是不可決定性nondeterminism)兩者皆為軟體性質。

沒人會為殺死一隻阿米巴變形蟲而掉眼淚,呃,大部分的人不會,同樣的,當你的程式印出“Hello, World!”後就離開main()並消失的無影無蹤,也不會有人會心情激動,但我想我們已經建立起一個共識,每一次執行“Hello, World!”程式就會產生出一段短短的意識活動。

呃…有幾分意味啦,一支“Hello, World!”,沒有迴圈也沒有分支,不具有任何不可決定性(除非由外界強加上去,例如一個亂數產生的硬體錯誤),所以你可以把它當做純硬體,只不過是用軟體程式寫出來,但在“Hello, World!”跟Hal 9000之間應該存在著一支擁有初步意識能力的軟體程式,若是這樣的話,把它關掉就等同於殺掉他一樣。

我們今日的軟體有那樣的複雜度嗎?夠複雜而且有足夠的自我感知能力,從倫理道德的觀點來看可以被認為具有意識嗎?答案大概是否定的,但我認為總有一天我們會達到那個地步,到那時,我真心希望我們不是用今日的方法在開發軟體,也就是編譯加上重新啟動的開發循環週期。

我想今日我們建造出來的軟體,大部分都像是在玩骨牌,這叫做骨牌設計法,你非常小心排著骨牌,只能跑一次,所有的骨牌都依照排好可預測的順序倒下,而且如果需要的話你將所有骨牌重新排一遍;最終結果可以比玩骨牌更精美更棒──想想玩具總動員裡面的一張一張畫面,本質上並沒有太大的差異,但的確更精美。

每一套真的很複雜的軟體,像是搜尋引擎或是電子商務系統,通常都是用骨牌設計法建構出來的,如果你主要以C++或Java寫程式,幾乎一定是用這種方法。

重新啟動一套骨牌系統是不可避免的動作,每有改變每次升級都需要重新啟動這樣的概念,跟這些系統在我們心中的形態緊緊相連,對我而言,我不喜歡骨牌,我個人想要跟一支變形蟲玩,而且如果他拒絕學習怎麼走迷宮或其他事物的話,我會把他壓扁然後發現得到新型阿米巴,不過在那之前我會先多想個兩次。

DWIM and QWAN

我的思考能力到現在已經有點模糊了,所以我盡量長話短說。DWIM是個可愛的頭字縮寫,我想是由Larry Wall發明的,意思是“Do What I Mean”,Perl社群常常使用這個字眼。

DWIM背後的概念是完美的軟體就好像一個從瓶子跑出來且滿懷感激讓你許願的精靈,我不是在說那些你對著噴泉或流星所許的不值錢願望,大家都知道你必須注意許願時的遣詞用字,因為許願精靈會以字面的意思給你要的,而不是你真正想要的,就好像所有你在使用的軟體一樣;相對的,DWIM精靈站在你這邊,可容忍文法或邏輯上的錯誤,不會得到錯誤的結果。(“一個男的走進酒吧,帶著迷你鋼琴跟一把十二英吋的鋼琴家…”)

每個程式設計師表面上都試著把DWIM寫進他們的軟體內,但幾乎都是用臆測的方式,事先假設使用者的行為會如何如何,有時候是以協力過濾(collaborative filtering)或其他以規則演算的方式來寫,但同樣也是會錯誤百出;唯一能讓DWIM不再只是驚鴻一瞥的方法是創造一支真正有智能(intelligent)的軟體──不僅是有意識,而是有智能,希望也有智慧和感知能力,以及(咽口水)友善的。

但這就會令人憂心了,大家都知道“智能”並不必然對你會產生有同理心,特別是你做了一些白目的事情。

所以到底要不要DWIM啊?就一方面而言,我們希望軟體可以更好,預測你想要的,回應你的需求,所以你可以過著你一直渴望的快樂幸福的生活,就另一方面而言,想達到這樣的境界的唯一方法是打造比我們更聰明的軟體,到那時,好一點的話我們要擔心會不會被邊緣化,壞一點的情況是我們要擔心會不會被奴役或被消滅掉。

我認為這解釋了,至少某部分解釋了,為什麼我們業界大部分都在打造越來越大的死系統來達到假假的DWIM(而且,打造這種大而麻木的系統,有什麼語言會比Java更好嗎?),死系統很安全,它們是決定性的且完全可控制的。

讓我們野心先別那麼大,省下口水先假設在我們有生之年不可能建造出有意識、有智能的軟體,那麼,比起現今我們大多數人產出的一般般的大便,有可能建造出比那更好的軟體嗎?

有另外一隻精靈叫做QWAN,建築師Christopher Alexander所發明的詞彙,意思是“Quality Without a Name”,而且(就跟這篇論文的其他難懂的觀點一樣)這是他早就了解的概念,終其一生不斷嘗試想把它描述出來跟找方法確實把它打造出來;簡短一點講的話,QWAN是一種無形無影“看見了自然就懂了”的空間或結構的特質,帶有 (a) 經由有機成長可以達到的境界,以及 (b) 詭秘地跟人類大腦中某部分硬體或軔體有關係,因為每個人都會有那靈光一閃抓到那模糊意義的時刻,但沒人真的知道為什麼會這樣。

在過去晦暗的時代,一些軟體專家們領悟到軟體也可以有QWAN,然後又發現在軟體跟從實體結構中想把它揪出來都一樣困難。

我在這裡主張,在之前列出我喜愛的系統,剛剛好在這些系統中可以找到明顯的QWAN,而我說那些缺乏重要特質的那些系統,都沒有QWAN的蹤影。(我之前有列舉出幾個較明顯的例子,然後含糊帶過其餘的,還記得嗎。)

人們都看出Emacs擁有QWAN這特質:美好、有生氣、舒服、正確,好像舊舊的牛仔褲那麼合身,猶如圖書館內火爐旁舒適溫軟的椅子;我現在說的恰恰是非常偏右腦的東西,情愫與感性:剛剛好這正是搞軟體的人很憋腳不擅長的,難怪我們不知道怎麼把這玩意放進軟體內,但不像使用者介面設計,軟體QWAN只可能從程式設計師手底下出現,因為對那些在你的軟體平台上開發的人來說,你擔任的角色就猶如室內裝潢師、主廚、人體工學顧問一樣。

那就是為何我認為大部分的軟體都是大便的原因,我不是在講一般使用者的使用經驗與感受,我是在講開發人員的開發經驗與感受(如果有的話),設計是一種藝術,不是科學,而且芸芸眾生皆非天生的藝術家,所以當QWAN出現在軟體系統中,即使只有一點點,也是很非凡的成就。

注意,順帶一提,軟體系統給一般使用者的使用經驗跟給開發人員的開發感受根本就是兩回事,Visual Studio提供相當精緻的使用者經驗,在這裡我說的使用者是指寫C++或C#程式碼的程式設計師(相對的另一方是,寫工具軟體幫助其他人寫程式,或是幫助別人把一些常做的事情自動化);Emacs讓使用者超難上手,但給開發人員的介面無與倫比好的不得了;Eclipse在兩者之間,但看得出來比較偏向一般使用者那一邊。

最後,QWAN有某部分是無法言喻的,把我的配料(命令列模式、擴充語言、advice系統、等等)加進去會讓系統更好,但不保證QWAN就會浮現出來,QWAN那些部分最終還是要回歸到品味(taste),既然品味人人皆不同,而且QWAN遠遠比不上DWIM那麼聰明,一個人的QWAN或許會是另外一個人的惡夢。

不過我依然認為我們應該試著將它打造出來。

型別系統(type systems)扮演的角色

太好了,來龍去脈說的夠多,我終於可以解釋一下我對型別系統的看法。

簡單講,型別系統是用來建造硬體的。每套軟體系統都有台機器在底下運轉──其實應該說有一整疊的機器,很多很多,從馮紐曼機器的CPU與暫存器與匯流排,往下到半導體元件,再到原子與夸克,以及量子力學的理論基礎。

不過我對硬體有更廣義的定義,硬體可以是任何事物,可以產出決定性的運算結果,非常硬而沒彈性,你必須先把它弄破才能做點修改要不然就要整個換新。所以,它並不只是由原子組成物理結構意義上的硬體;就我的觀點看來,一台機器的硬體部分也包含了那些需要重新啟動才能做修改的軟體。

型別系統的概念跟“編譯期間”與“靜態檢查”緊緊相連,我對型別系統也有非常廣的定義,我認為所有加諸在運算上的限制都是某種型別系統,包括scoping跟visibility(public/private/protected/friend/...)的限制,安全性限制,甚至程式的基本運算架構也算,我的看法中的“靜態”型別檢查其實包括了某些發生在執行週期的檢查。

但當談到靜態型別系統時,大部分人指的都是編譯週期的檢查與驗證(語言的語法中的型別標示,從AST建立推論出來的型別資訊),這些是在執行時就會不見了的東西。

跟把限制與檢查延到執行時才做相比較,靜態型別系統有一些優點,其一,程式碼跑的比較快,因為如果你執行前就做好檢查的動作,而且你若能保證執行時程式不會有改變,那你就不必在執行時做檢查,另外一個,靜態的限制條件,通常都可以用編譯器之外的工具加以分析(例如你的IDE),這可以幫助你了解程式執行的流程為何。

真是太棒啦。

靜態型別系統沒什麼不好,只是你必須知道,當你用它時,你是在建造硬體,而非軟體。

對一個寫好的程式而言,靜態型別系統絕不會對執行效能有所影響:不論系統提供多少的型別可用,產生出來的機械碼是一樣的,有個恰當的例子:C就只有那麼一點點最基本的型別,但C++程式不會比C跑的更快,C沒有動態型別檢查(dynamically typed),但也不太能算是靜態型別,單單用C也可以寫出又快又穩的程式。(Emacs跟Linux核心就是兩個好典範。)

靜態型別系統的正確使用時機是當我們想把軟體固定下來變成硬體的時候;每當一組運算法則(例如OpenGL繪圖指令)變得很普遍很穩定時,就值得犧牲一些彈性來將它轉到機械層以獲得較高的效能,也就是轉成硬體,但通常效能最佳化會對彈性造成很大的損害(其中非常小的一部分是標準化過程所帶來的,我想)。

因為眼下大部分的程序員都偏愛打造牽線木偶而不是會讓人恐慌的真人小孩,大部分的程式設計傾向於建構一層又一層的硬體;C++跟Java程式設計師們(我想C#也是)基本上在一開始就用型別系統來塑模任何問題,也就等於把每一行程式都轉成硬體的形式。

僅用陣列、鏈結串列、雜湊、樹結構與函式就可以用C++跟Java寫出鬆散型別檢查(loosely-typed)的程式,但這樣會被兩個陣營的人強烈抨擊(或至少皺眉不屑),在兩個陣營裡,嚴格型別檢查跟詳細塑模設計在過去十年來逐漸成長成主流,他們認為型別檢查鬆散的程式會危害生產力(也就是指會影響開發時程),也會危害彈性(也是指會影響時程表,因為沒彈性表示很難納進新的功能)。

Hindley-Milner型別系統(源於學界,幾乎打不進業界)的虔誠教徒相信H-M型別系統遠遠優於Java或C++的,因為(理由之一)它漏洞較少。

H-M不被業界所使用,各位,聽我說,正是因為它沒有任何漏洞。如果你正在開發軟體,但你相信(同大多數人)開發方法是在一個長又痛苦的循環中不斷地建造硬體然後丟棄,直到那塊硬體是你想要的東西才停止,那麼有時候你會需要掙脫型別系統的限制:告訴型別系統閉上鳥嘴、我知道我在做什麼、我的程式寫的沒錯正是我想要的,型別轉型(type casts)、縮減或放寬的轉換、用friend functions來繞過類別的存取保護機制、把自訂的迷你語言放進字串內然後自己手動解析,有數十種方法可以跳過Java跟C++的型別系統檢查機制,而且程式設計師們一直都有在用,因為(很少人知道這點)他們真正試著建造的是軟體,並不是硬體。

以沒路用的形式上的數學觀點看來,H-M是很優美的,它處理起某些運算結構與概念特別巧妙;在Haskell、SML、OCaml可以找到的樣式比對後分發(pattern matching dispatch)非常非常好用,但不意外地,它處理起其他一般常見且大家都需要用的運算結構與概念非常笨拙,不過那些擁護者反而辯解說成是你錯了、其實你不需要這些東西,例如像是,你知道的,設定變數這種基本動作,你才不想要這種東西咧,相信他們吧。(OCaml讓你做,但提到這點時他們的目光都下垂看著自己的鼻子。)

沒錯,加上一點努力,你可以用Haskell打造出漂亮柔軟的牽線木偶,但永遠就是個小木偶皮諾丘,絕不會有好心腸精靈讓你美夢成真變成小男孩,Haskell中幾乎沒有動態程式碼載入或是執行週期的反射機制(runtime reflection)這些東西;這些概念對他們來說太怪異了,以至於連想跟他們討論這些概念為什麼有用都很困難:他們的世界觀裡沒有軟體是活生生的可以成長可以呼吸這樣的思想。

讓我們來看看動態型別檢查的天地裡稍微不那麼體面的語言,大家都知道很多程序員都把動態語言跟玩具看做同一類;主要流行的動態語言包括Perl、PHP、Python、Visual Basic、JavaScript、Ruby、Tcl跟Lua,除了這些語言設計者應得的尊敬之外,我不得不說他們在學生時代的編譯器課程應該是被當掉的吧,舉個例:他們都沒弄好lexical scoping;諷刺的是,他們的腳本語言放到鎂光燈下後反而讓他們(Larry, Guido, Matz, Brendan, ...)搖身一變成為世界級的語言設計者,這也讓他們,跟Salieri一樣,能夠以大部分人不了解的觀點來理解領會他們設計語言的不足與弱點(現在想修正已經太太困難了)。

Scheme大有前途,但它有個致命缺陷,就是它不能成長,跟已被業界廣泛採用的語言與平台情況不同,它們是為了保住廣大的使用群,Scheme是為了維持小而美才能佔據它的利基市場,也就是用在電腦科學教學課程上。

多方看來Common Lisp是個理想的選擇:它是動態型別檢查的語言,但可選擇性地加上型態標記(換句話說,你建造軟體,然後可以選擇要不要轉成硬體 ),很多好工具可用,說明文件也都有,具有好軟體之必備特質,我在這篇文章舉出來的它都有,而且還有相當程度的QWAN,然而,它已經停止繼續發展了,程序員就像鯊魚嗅到一樣敏感察覺到它沒氣勢了,Common Lisp擁有的能量跟William F. Buckley, Jr.和Charleton Heston激辯不相上下。(我看過一次,我發誓他們兩個一定有一半時間在打呼)。


單純的Lisp缺少了C的語法結構,在C語法根絕之前:這項任務可能要花上五十年,我們絕不可能在電腦運算與語言設計上有什麼真正的進展,在那之前若想往前走,唯一之道是把程式語言中的模型(AST)與表示(語法)分離開來,允許語法外衣,讓愛用者們繼續用C語法直到他們全死光,Common Lisp本可以加進額外的語法規則,但它既然已不再成長,也就不可能發生。我會到別處尋找下一個主流物(the Next Big Thing)。

結論


時間是凌晨三點三十分,已經超出我每月部落格寫作限制時間兩小時了,你下個月或許會在我這看到一篇簡短的部落格!不論如何,我想我已經把胸中的不吐不快都寫下來了。


我想,我們的理想是軟體能夠按照你的意思執行動作,不過因為DWIM只可能發生在真正有智能的軟體上,而想要依據變動中的世界來預測軟體該怎麼寫太困難了,話雖如此,我認為那是我們想要的。

我懷疑短時間內不可能有那樣的東西出現,因為大部分的程序員一股腦地專注在打造硬體上,勤奮地將型別系統使用在他們的軟體上,太過度了反而搞壞了原本還不錯的軟體。

在魔法油燈變出DWIM前,我認為我們應該建造有生命的軟體,或許只是像樹一樣的生物,但那就足以喚醒其體內的QWAN,任何只要有一丁點的QWAN的系統都真的很棒、值得使用──至少對寫程式的人來說;一般使用者對這些系統對評價參差不齊。

有生命的軟體,擁有命令列模式,因為你需要能像個成年人一樣跟他說話,擁有擴充語言,因為要能夠幫助他成長,擁有advice系統,因為你需要訓練跟修正他,擁有一塊利基市場,因為他需要使用者們才能成長茁壯,擁有外掛系統,這樣你才能幫他打扮參加舞會,而且他是有自我察覺(self-aware)能力的,能力的最大值只會受限於外在效能的約束,這些特質都必須巧妙優雅地整合在一起,花在每個子系統上的心力與詳細程度都跟整個系統一樣。

而且你不應該在每當需要修改時就要謀殺它一遍,如果你對待你的軟體像個活跳跳的生物,那麼最終他就會開始變得像個真的。

如果我們能喚醒真的人工智慧,我想皮諾丘是個適合的名字。

2010/10/11

讀後推薦:程式設計師的自我修養:連結、載入、程式庫 by 俞甲子/石凡/潘愛民

我看了這本書,覺得還不錯,對我很有幫助,釐清了很多概念與疑問。

書名:程式設計師的自我修養:連結、載入、程式庫
作者:俞甲子/石凡/潘愛民
出版社:碁峯




史瑞克說妖怪像洋蔥一樣,是有層次的,資訊科學這玩意兒更是如此,從上到下不知有多少層,有應用軟體層、有整合開發套件跟永遠看不完的類別庫與函式庫、有作業系統提供的API跟系統呼叫、有軔體層、有硬體層,每層都還可以繼續細分下去,另外還有網路服務跟雲端運算,各種數也數不清的結構與架構,嘿嘿,看來咱們比妖怪更有層次啊。




這本書的主旨就是副標題所說的,關於軟體程式從寫好開始,編譯成組合語言,組譯成機器指令,加上執行階段程式庫靜態連結成執行檔,執行時由作業系統載入到記憶體裡,動態載入與連結所需的程式庫,這一連串的過程;這本書解說了在x86架構下的ELF檔與PE檔格式,分別是在Linux與Windows系統下所使用的檔案格式,用來儲存執行檔(無附檔名或.exe)、中間目的檔(.o or .obj)、靜態程式庫(.a or .lib)、動態程式庫(.so or .dll)等等,書中有概念的闡述,也分解簡單的小程式來當實例,對我來說,幫助我釐清了很多觀念,例如什麼叫做runtime(runtime environment, runtime library, 執行階段程式庫)、編譯器之後的靜態連結器要處理哪些東西、動態程式庫(.so or .dll)背後的道理是什麼、符號表(symbol table)到底有多重要、在main()執行之前作業系統做了哪些事情、等等,真是太棒了,好書一本。



有一些書,偏重理論與概念,例如鐵甲武士惡鬥吐火龍,對於一個在應用軟體層的人來說,較不實用,我想要的是實際上用的工具與技術(compiler, dynamic linking, etc..)的概略說明,我想要的是當前廣泛使用的作業系統(Linux, Windows)與工具(gcc, Visual C++)與檔案格式(ELF, PE)的探討,當理論書籍說有哪些方法可以用來實作出什麼什麼技術,本書則告訴你Linux與Windows用的技術是什麼;這樣當我在其上開發軟體時,才不會被華麗文藻包裝過的說明文件迷惑,搞不懂共用物件與plug-in的差別。

PS 不要誤會,龍書是經典,編譯器也是資訊科學必修的課程。



還記得當初寫第一支程式的時候嗎?從寫好到執行看到螢幕上印出hello, world.,其實中間經過了許許多多的關卡,編譯器、組譯器、靜態連結器、作業系統載入、動態連結、虛擬記憶體系統、等等,不說不知道,稍稍往下鑽研就會被嚇到,疊床架屋一層又一層的技術,才能讓我們鍵入少少幾行程式按下個按鈕就可以寫出hello, world.,當我們在其上享受各種新式技術與便利之餘,也別忘了要了解一下底層的原理,因為三不五時,底層的東西會跑上來嚇人,有時候程式當的亂七八糟,可是又看不懂錯誤訊息,有時候甚至連錯誤訊息都沒有,這時若心理有底層技術的概念,解決問題的機會就會多個幾趴。




如作者所言,關於載入器(loader)與連結器(linker)的資料是不多的,就算有也是那種看起來很硬很技術性的著作,有這麼一本算是研究心得的著作,要謝謝作者所花的心力。