Unix哲學相關(guān)資源匯總
Unix哲學起源于Ken Thompson早期關(guān)于如何設(shè)計一個服務(wù)接口簡潔、小巧精干的操作系統(tǒng)的思考,隨著Unix文化在學習如何盡可能發(fā)掘Thompson設(shè)計思想的過程中不斷成長,同時一路上還從其它許多地方博采眾長。
Unix哲學說來不算是一種正規(guī)設(shè)計方法。它并不打算從計算機科學的理論高度來產(chǎn)生理論上完美的軟件。那些毫無動力、松松垮垮而且薪水微薄的程序員們,能在短短期限內(nèi),如同神靈附體般造出穩(wěn)定而新穎的軟件——這只不過是經(jīng)理人永遠的夢囈罷了。
Unix哲學(同其它工程領(lǐng)域的民間傳統(tǒng)一樣)是自下而上的,而不是自上而下的。Unix哲學注重實效,立足于豐富的經(jīng)驗。你不會在正規(guī)方法學和標準中找到它,它更接近于隱性的半本能的知識,即Unix文化所傳播的專業(yè)經(jīng)驗。它鼓勵那種分清輕重緩急的感覺,以及懷疑一切的態(tài)度,并鼓勵你以幽默達觀的態(tài)度對待這些。
Unix管道的發(fā)明人、Unix傳統(tǒng)的奠基人之一Doug McIlroy在[McIlroy78]中曾經(jīng)說過:
(i)讓每個程序就做好一件事。如果有新任務(wù),就重新開始,不要往原程序中加入新功能而搞得復雜。
(ii)假定每個程序的輸出都會成為另一個程序的輸入,哪怕那個程序還是未知的。輸出中不要有無關(guān)的信息干擾。避免使用嚴格的分欄格式和二進制格式輸入。不要堅持使用交互式輸入。
(ⅲ)盡可能早地將設(shè)計和編譯的軟件投入試用, 哪怕是操作系統(tǒng)也不例外,理想情況下, 應(yīng)該是在幾星期內(nèi)。對拙劣的代碼別猶豫,扔掉重寫。
(iv)優(yōu)先使用工具而不是拙劣的幫助來減輕編程任務(wù)的負擔。工欲善其事,必先利其器。
后來他這樣總結(jié)道(引自《Unix的四分之一世紀》(A Quarter Century of Unix [Salus])):
Unix哲學是這樣的:一個程序只做一件事,并做好。程序要能協(xié)作。程序要能處理文本流,因為這是最通用的接口。
Rob Pike, 最偉大的C語言大師之一, 在《Notes on C Programming》中從另一個稍微不同的角度表述了Unix的哲學[Pike]:
原則1:你無法斷定程序會在什么地方耗費運行時間。瓶頸經(jīng)常出現(xiàn)在想不到的地方,所以別急于胡亂找個地方改代碼,除非你已經(jīng)證實那兒就是瓶頸所在。
原則2:估量。在你沒對代碼進行估量,特別是沒找到最耗時的那部分之前,別去優(yōu)化速度。
原則3:花哨的算法在n很小時通常很慢,而n通常很小?;ㄉ谒惴ǖ某?shù)復雜度很大。除非你確定n總是很大,否則不要用花哨算法(即使n很大,也優(yōu)先考慮原則2)。
原則4:花哨的算法比簡單算法更容易出bug、更難實現(xiàn)。盡量使用簡單的算法配合簡單的數(shù)據(jù)結(jié)構(gòu)。
原則5:數(shù)據(jù)壓倒一切。如果已經(jīng)選擇了正確的數(shù)據(jù)結(jié)構(gòu)并且把一切都組織得井井有條,正確的算法也就不言自明。編程的核心是數(shù)據(jù)結(jié)構(gòu),而不是算法。
原則6:沒有原則6。
Ken Thompson——Unix最初版本的設(shè)計者和實現(xiàn)者,禪宗偈語般地對Pike的原則4作了強調(diào):
拿不準就窮舉。
Unix哲學中更多的內(nèi)容不是這些先哲們口頭表述出來的,而是由他們所作的一切和Unix本身所作出的榜樣體現(xiàn)出來的。從整體上來說,可以概括為以下幾點:
1. 模塊原則:使用簡潔的接口拼合簡單的部件。
2. 清晰原則:清晰勝于機巧。
3. 組合原則:設(shè)計時考慮拼接組合。
4. 分離原則:策略同機制分離,接口同引擎分離。
5. 簡潔原則:設(shè)計要簡潔,復雜度能低則低。
6. 吝嗇原則:除非確無它法,不要編寫龐大的程序。
7. 透明性原則:設(shè)計要可見,以便審查和調(diào)試。
8. 健壯原則:健壯源于透明與簡潔。
9. 表示原則:把知識疊入數(shù)據(jù)以求邏輯質(zhì)樸而健壯。
10. 通俗原則:接口設(shè)計避免標新立異。
11. 緘默原則:如果一個程序沒什么好說的,就沉默。
12. 補救原則:出現(xiàn)異常時,馬上退出并給出足夠錯誤信息。
13. 經(jīng)濟原則:寧花機器一分,不花程序員一秒。
14. 生成原則:避免手工hack,盡量編寫程序去生成程序。
15. 優(yōu)化原則:雕琢前先要有原型,跑之前先學會走。
16. 多樣原則:決不相信所謂“不二法門”的斷言。
17. 擴展原則:設(shè)計著眼未來,未來總比預想來得快。
如果剛開始接觸Unix,這些原則值得好好體味一番。談軟件工程的文章常常會推薦大部分的這些原則,但是大多數(shù)其它操作系統(tǒng)缺乏恰當?shù)墓ぞ吆蛡鹘y(tǒng)將這些準則付諸實踐,所以,多數(shù)的程序員還不能自始至終地貫徹這些原則。蹩腳的工具、糟糕的設(shè)計、過度的勞作和臃腫的代碼對他們已經(jīng)是家常便飯了;他們奇怪,Unix的玩家有什么好煩的呢。
1.6.1 模塊原則:使用簡潔的接口拼合簡單的部件
正如Brian Kernighan曾經(jīng)說過的:“計算機編程的本質(zhì)就是控制復雜度”[Kernighan-Plauger]。排錯占用了大部分的開發(fā)時間,弄出一個拿得出手的可用系統(tǒng),通常與其說出自才華橫溢的設(shè)計成果,還不如說是跌跌撞撞的結(jié)果。
匯編語言、編譯語言、流程圖、過程化編程、結(jié)構(gòu)化編程、所謂的人工智能、第四代編程語言、面向?qū)ο?、以及軟件開發(fā)的方法論,不計其數(shù)的解決之道被拋售者吹得神乎其神。但實際上這些都用處不大,原因恰恰在于它們“成功”地將程序的復雜度提升到了人腦幾乎不能處理的地步。就像Fred Brooks的一句名言[Brooks]:沒有萬能藥。
要編制復雜軟件而又不至于一敗涂地的唯一方法就是降低其整體復雜度——用清晰的接口把若干簡單的模塊組合成一個復雜軟件。如此一來,多數(shù)問題只會局限于某個局部,那么就還有希望對局部進行改進而不至牽動全身。
1.6.2 清晰原則: 清晰勝于機巧
維護如此重要而成本如此高昂;在寫程序時,要想到你不是寫給執(zhí)行代碼的計算機看的,而是給人——將來閱讀維護源碼的人,包括你自己——看的。
在Unix傳統(tǒng)中,這個建議不僅意味著代碼注釋。良好的Unix實踐同樣信奉在選擇
算法和實現(xiàn)時就應(yīng)該考慮到將來的可擴展性。而為了取得程序一丁點的性能提升就大幅度增加技術(shù)的復雜性和晦澀性,這個買賣做不得——這不僅僅是因為復雜的代碼容易滋生bug,也因為它會使日后的閱讀和維護工作更加艱難。
相反,優(yōu)雅而清晰的代碼不僅不容易崩潰——而且更易于讓后來的修改者立刻理解。這點非常重要,尤其是說不定若干年后回過頭來修改這些代碼的人可能恰恰就是你自己。
永遠不要去吃力地解讀一段晦澀的代碼三次。第一次也許僥幸成功,但如果發(fā)現(xiàn)必須重新解讀一遍——離第一次太久了,具體細節(jié)無從回想——那么你該注釋代碼了,這樣第三次就相對不會那么痛苦了。
—Henry Spencer
1.6.3 組合原則:設(shè)計時考慮拼接組合
如果程序彼此之間不能有效通信,那么軟件就難免會陷入復雜度的泥淖。
在輸入輸出方面,Unix傳統(tǒng)極力提倡采用簡單、文本化、面向流、設(shè)備無關(guān)的格式。在經(jīng)典的Unix下,多數(shù)程序都盡可能采用簡單過濾器的形式,即將一個輸入的簡單文本流處理為一個簡單的文本流輸出。
拋開世俗眼光,Unix程序員偏愛這種做法并不是因為他們仇視圖形用戶界面,而是因為如果程序不采用簡單的文本輸入輸出流,它們就極難銜接。
Unix中,文本流之于工具,就如同在面向?qū)ο蟓h(huán)境中的消息之于對象。文本流界面的簡潔性加強了工具的封裝性。而許多精致的進程間通訊方法,比如遠程過程調(diào)用,都存在牽扯過多各程序間內(nèi)部狀態(tài)的傾向。
要想讓程序具有組合性,就要使程序彼此獨立。在文本流這一端的程序應(yīng)該盡可能不要考慮文本流另一端的程序。將一端的程序替換為另一個截然不同的程序,而完全不驚擾另一端應(yīng)該很容易做到。
GUI可以是個好東西。有時竭盡所能也不可避免復雜的二進制數(shù)據(jù)格式。但是,在做一個GUI前,最好還是應(yīng)該想想可不可以把復雜的交互程序跟干粗活的算法程序分離開,每個部分單獨成為一塊,然后用一個簡單的命令流或者是應(yīng)用協(xié)議將其組合在一起。
在構(gòu)思精巧的數(shù)據(jù)傳輸格式前,有必要實地考察一下,是否能利用簡單的文本數(shù)據(jù)格式;以一點點格式解析的代價,換得可以使用通用工具來構(gòu)造或解讀數(shù)據(jù)流的好處是值得的。
當程序無法自然地使用序列化、協(xié)議形式的接口時,正確的Unix設(shè)計至少是,把盡可能多的編程元素組織為一套定義良好的API。這樣,至少你可以通過鏈接調(diào)用應(yīng)用程序,或者可以根據(jù)不同任務(wù)的需求粘合使用不同的接口。
(我們將在第7章詳細討論這些問題。)
1.6.4 分離原則: 策略同機制分離,接口同引擎分離
在Unix之失的討論中,我們談到過X系統(tǒng)的設(shè)計者在設(shè)計中的基本抉擇是實行“機制,而不是策略”這種做法——使X成為一個通用圖形引擎,而將用戶界面風格留給工具包或者系統(tǒng)的其它層次來決定。這一點得以證明是正確的,因為策略和機制是按照不同的時間尺度變化的,策略的變化要遠遠快于機制。GUI工具包的觀感時尚來去匆匆,而光柵操作和組合卻是永恒的。
所以,把策略同機制揉成一團有兩個負面影響:一來會使策略變得死板,難以適應(yīng)用戶需求的改變,二來也意味著任何策略的改變都極有可能動搖機制。
相反,將兩者剝離,就有可能在探索新策略的時候不足以打破機制。另外,我們也可以更容易為機制寫出較好的測試(因為策略太短命,不值得花太多精力在這上面)。
這條設(shè)計準則在GUI環(huán)境之外也被廣泛應(yīng)用??偠灾@條準則告訴我們應(yīng)該設(shè)法將接口和引擎剝離開來。
實現(xiàn)這種剝離的一個方法是,比如,將應(yīng)用按照一個庫來編寫,這個庫包含許多由內(nèi)嵌腳本語言驅(qū)動的C服務(wù)程序,而至于整個應(yīng)用的控制流程則用腳本來撰寫而不是用C語言。這種模式的經(jīng)典例子就是Emacs編輯器,它使用內(nèi)嵌的腳本語言Lisp解釋器來控制用C編寫的編輯原語操作。我們會在第11章討論這種設(shè)計風格。
另一個方法是將應(yīng)用程序分成可以協(xié)作的前端和后端進程,通過套接字上層的專用應(yīng)用協(xié)議進行通訊;我們會在第5章和第7章討論這種設(shè)計。前端實現(xiàn)策略,后端實現(xiàn)
機制。比起僅用單個進程的整體實現(xiàn)方式來說,這種雙端設(shè)計方式大大降低了整體復雜度,bug有望減少,從而降低程序的壽命周期成本。
1.6.5 簡潔原則:設(shè)計要簡潔,復雜度能低則低
來自多方面的壓力常常會讓程序變得復雜(由此代價更高,bug更多),其中一種壓力就是來自技術(shù)上的虛榮心理。程序員們都很聰明,常常以能玩轉(zhuǎn)復雜東西和耍弄抽象概念的能力為傲,這一點也無可厚非。但正因如此,他們常常會與同行們比試,看看誰能夠鼓搗出最錯綜復雜的美妙事物。正如我們經(jīng)常所見,他們的設(shè)計能力大大超出他們的實現(xiàn)和排錯能力,結(jié)果便是代價高昂的廢品。
“錯綜復雜的美妙事物”聽來自相矛盾。Unix程序員相互比的是誰能夠做到“簡潔而漂亮”并以此為榮,這一點雖然只是隱含在這些規(guī)則之中,但還是很值得公開提出來強調(diào)一下。
—Doug McIlroy
更為常見的是(至少在商業(yè)軟件領(lǐng)域里),過度的復雜性往往來自于項目的要求,而這些要求常常基于當月的推銷熱點,而不是基于顧客的需求和軟件實際能夠提供的功能。許多優(yōu)秀的設(shè)計被市場推銷所需要的大堆大堆“特性清單”扼殺——實際上,這些特性功能幾乎從未用過。然后,惡性循環(huán)開始了:比別人花哨的方法就是把自己變得更花哨。很快,龐大臃腫變成了業(yè)界標準,每個人都在使用臃腫不堪、bug極多的軟件,連軟件開發(fā)人員也不敢敝帚自珍。
無論以上哪種方式,最后每個人都是失敗者。
要避免這些陷阱,唯一的方法就是鼓勵另一種軟件文化,以簡潔為美,人人對龐大復雜的東西群起而攻之——這是一個非??粗睾唵谓鉀Q方案的工程傳統(tǒng),總是設(shè)法將程序系統(tǒng)分解為幾個能夠協(xié)作的小部分,并本能地抵制任何用過多噱頭來粉飾程序的企圖。
這就有點Unix文化的意味了。
1.6.6 吝嗇原則: 除非確無它法,不要編寫龐大的程序
“大”有兩重含義:體積大,復雜程度高。程序大了,維護起來就困難。由于人們對花費了大量精力才做出來的東西難以割舍,結(jié)果導致在龐大的程序中把投資浪費在注定要失敗或者并非最佳的方案上。
(我們會在第13章就軟件的最佳大小進行更多的詳細討論。)
1.6.7 透明性原則:設(shè)計要可見,以便審查和調(diào)試
因為調(diào)試通常會占用四分之三甚至更多的開發(fā)時間,所以一開始就多做點工作以減少日后調(diào)試的工作量會很劃算。一個特別有效的減少調(diào)試工作量的方法就是設(shè)計時充分考慮透明性和顯見性。
軟件系統(tǒng)的透明性是指你一眼就能夠看出軟件是在做什么以及怎樣做的。顯見性指程序帶有監(jiān)視和顯示內(nèi)部狀態(tài)的功能,這樣程序不僅能夠運行良好,而且還可以看得出它以何種方式運行。
設(shè)計時如果充分考慮到這些要求會給整個項目全過程都帶來好處。至少,調(diào)試選項的設(shè)置應(yīng)該盡量不要在事后,而應(yīng)該在設(shè)計之初便考慮進去。這是考慮到程序不但應(yīng)該能夠展示其正確性,也應(yīng)該能夠把原開發(fā)者解決問題的思維模型告訴后來者。
程序如果要展示其正確性,應(yīng)該使用足夠簡單的輸入輸出格式,這樣才能保證很容易地檢驗有效輸入和正確輸出之間的關(guān)系是否正確。
出于充分考慮透明性和顯見性的目的,還應(yīng)該提倡接口簡潔,以方便其它程序?qū)ζ溥M行操作——尤其是測試監(jiān)視工具和調(diào)試腳本。
1.6.8 健壯原則: 健壯源于透明與簡潔
軟件的健壯性指軟件不僅能在正常情況下運行良好,而且在超出設(shè)計者設(shè)想的意外條件下也能夠運行良好。
大多數(shù)軟件禁不起磕碰,毛病很多,就是因為過于復雜,很難通盤考慮。如果不能夠正確理解一個程序的邏輯,就不能確信其是否正確,也就不能在出錯的時候修復它。
這也就帶來了讓程序健壯的方法,就是讓程序的內(nèi)部邏輯更易于理解。要做到這一點主要有兩種方法:透明化和簡潔化。
就健壯性而言,設(shè)計時要考慮到能承受極端大量的輸入,這一點也很重要。這時牢記組合原則會很有益處;經(jīng)不起其它一些程序產(chǎn)生的輸入(例如,原始的Unix C編譯器據(jù)說需要一些小小的升級才能處理好Yacc的輸出)。當然,這其中涉及的一些形式對人類來說往往看起來沒什么實際用處。比如,接受空的列表/字符串等等,即使在人們很少或者根本就不提供空字符串的地方也得如此,這可以避免在用機器生成輸入時需要對這種情況進行特殊處理。
—Henry Spencer
在有異常輸入的情況下,保證軟件健壯性的一個相當重要的策略就是避免在代碼中出現(xiàn)特例。bug通常隱藏在處理特例的代碼以及處理不同特殊情況的交互操作部分的代碼中。
上面我們曾說過,軟件的透明性就是指一眼就能夠看出來是怎么回事。如果“怎么回事”不算復雜,即人們不需要絞盡腦汁就能夠推斷出所有可能的情況,那么這個程序就是簡潔的。程序越簡潔,越透明,也就越健壯.
模塊性(代碼簡樸,接口簡潔)是組織程序以達到更簡潔目的的一個方法。另外也有其它的方法可以得到簡潔。接下來就是另一個。
1.6.9 表示原則: 把知識疊入數(shù)據(jù)以求邏輯質(zhì)樸而健壯
即使最簡單的程序邏輯讓人類來驗證也很困難,但是就算是很復雜的數(shù)據(jù),對人類來說,還是相對容易地就能夠推導和建模的。不信可以試試比較一下,是五十個節(jié)點的指針樹,還是五十行代碼的流程圖更清楚明了;或者,比較一下究竟用一個數(shù)組初始化器來表示轉(zhuǎn)換表,還是用switch語句更清楚明了呢?可以看出,不同的方式在透明性和清晰性方面具有非常顯著的差別。參見Rob Pike的原則5。
數(shù)據(jù)要比編程邏輯更容易駕馭。所以接下來,如果要在復雜數(shù)據(jù)和復雜代碼中選擇一個,寧愿選擇前者。更進一步:在設(shè)計中,你應(yīng)該主動將代碼的復雜度轉(zhuǎn)移到數(shù)據(jù)之中去。
此種考量并非Unix社區(qū)的原創(chuàng),但是許多Unix代碼都顯示受其影響。特別是C語言對指針使用控制的功能,促進了在內(nèi)核以上各個編碼層面上對動態(tài)修改引用結(jié)構(gòu)。在
結(jié)構(gòu)中用非常簡單的指針操作就能夠完成的任務(wù),在其它語言中,往往不得不用更復雜的過程才能完成。
(我們將在第9章再討論這些技術(shù)。)
1.6.10 通俗原則:接口設(shè)計避免標新立異
(也就是眾所周知的“最少驚奇原則”。)
最易用的程序就是用戶需要學習新東西最少的程序——或者,換句話說,最易用的程序就是最切合用戶已有知識的程序。
因此,接口設(shè)計應(yīng)該避免毫無來由的標新立異和自作聰明。如果你編制一個計算器程序,‘+’應(yīng)該永遠表示加法。而設(shè)計接口的時候,盡量按照用戶最可能熟悉的同樣功能接口和相似應(yīng)用程序來進行建模。
關(guān)注目標受眾。他們也許是最終用戶,也許是其他程序員,也許是系統(tǒng)管理員。對于這些不同的人群,最少驚奇的意義也不同。
關(guān)注傳統(tǒng)慣例。Unix世界形成了一套系統(tǒng)的慣例,比如配置和運行控制文件的格式,命令行開關(guān)等等。這些慣例的存在有個極好的理由:緩和學習曲線。應(yīng)該學會并使用這些慣例。
(我們將在第5章和第10章討論這些傳統(tǒng)慣例。)
最小立異原則的另一面是避免表象相似而實際卻略有不同。這會極端危險,因為表象相似往往導致人們產(chǎn)生錯誤的假定。所以最好讓不同事物有明顯區(qū)別,而不要看起來幾乎一模一樣。
—Henry Spencer
1.6.11 緘默原則:如果一個程序沒什么好說的,就保持沉默
Unix中最古老最持久的設(shè)計原則之一就是:若程序沒有什么特別之處可講,就保持沉默。行為良好的程序應(yīng)該默默工作,決不嘮嘮叨叨,礙手礙腳。沉默是金。
“沉默是金”這個原則的起始是源于Unix誕生時還沒有視頻顯示器。在1969年的緩慢的打印終端,每一行多余的輸出都會嚴重消耗用戶的寶貴時間?,F(xiàn)在,這種情況已不復存在,一切從簡的這個優(yōu)良傳統(tǒng)流傳至今。
我認為簡潔是Unix程序的核心風格。一旦程序的輸出成為另一個程序的輸入,就很容易把需要的數(shù)據(jù)挑出來。站在人的角度上來說――重要信息不應(yīng)該混雜在冗長的程序內(nèi)部行為信息中。如果顯示的信息都是重要的,那就不用找了。
—Ken Arnold
設(shè)計良好的程序?qū)⒂脩舻淖⒁饬σ暈橛邢薜膶氋F資源,只有在必要時才要求使用。
(我們將在第11章末尾進一步討論緘默原則及其理由。)
1.6.12 補救原則: 出現(xiàn)異常時,馬上退出并給出足量錯誤信息
軟件在發(fā)生錯誤的時候也應(yīng)該與在正常操作的情況下一樣,有透明的邏輯。最理想的情況當然是軟件能夠適應(yīng)和應(yīng)付非正常操作;而如果補救措施明明沒有成功,卻悄無聲息地埋下崩潰的隱患,直到很久以后才顯現(xiàn)出來,這就是最壞的一種情況。
因此,軟件要盡可能從容地應(yīng)付各種錯誤輸入和自身的運行錯誤。但是,如果做不到這一點,就讓程序盡可能以一種容易診斷錯誤的方式終止。
同時也請注意Postel的規(guī)定[8]:“寬容地收,謹慎地發(fā)”。Postel談的是網(wǎng)絡(luò)服務(wù)程序,但是其含義可以廣為適用。就算輸入的數(shù)據(jù)很不規(guī)范,一個設(shè)計良好的程序也會盡量領(lǐng)會其中的意義,以盡量與別的程序協(xié)作;然后,要么響亮地倒塌,要么為工作鏈下一環(huán)的程序輸出一個嚴謹干凈正確的數(shù)據(jù)。
然而,也請注意這條警告:
最初HTML文檔推薦“寬容地接受數(shù)據(jù)”,結(jié)果因為每一種瀏覽器都只接受規(guī)范中一個不同的超集,使我們一直倍感無奈。要寬容的應(yīng)該是規(guī)范而不是它們的解釋工具。
—Doug McIlroy
McIlroy 要求我們在設(shè)計時要考慮寬容性,而不是用過分縱容的實現(xiàn)來補救標準的不足。否則,正如他所指出的一樣,一不留神你會死得很難看。
1.6.13 經(jīng)濟原則: 寧花機器一分,不花程序員一秒
在Unix早期的小型機時代,這一條觀點還是相當激進的(那時機器要比現(xiàn)在慢得多也貴得多)。如今,隨著技術(shù)的發(fā)展,開發(fā)公司和大多數(shù)用戶(那些需要對核爆炸進行建?;蛱幚砣S電影動畫的除外)都能夠得到廉價的機器,所以這一準則的合理性就顯然不用多說啦!
但不知何故,實踐似乎還沒完全跟上現(xiàn)實的步伐。如果我們在整個軟件開發(fā)中很嚴格的遵循這條原則的話,大多數(shù)的應(yīng)用場合都應(yīng)該使用高一級的語言,如Perl、Tcl、Python、Java、Lisp,甚至shell——這些語言可以將程序員從自行管理內(nèi)存的負擔中解放出來(參見[Ravenbrook])。
這種做法在Unix世界中已經(jīng)開始施行,盡管Unix之外的大多數(shù)軟件商仍堅持采用舊Unix學派的C(或C++)編碼方法。本書會在后面詳細討論這個策略及其利弊權(quán)衡。
另一個可以顯著節(jié)約程序員時間的方法是:教會機器如何做更多低層次的編程工作,這就引出了……
1.6.14 生成原則: 避免手工hack,盡量編寫程序去生成程序
眾所周知,人類很不善于干辛苦的細節(jié)工作。因此,程序中的任何手工hacking都是滋生錯誤和延誤的溫床。程序規(guī)格越簡單越抽象,設(shè)計者就越容易做對。由程序生成代碼幾乎(在各個層次)總是比手寫代碼廉價并且更值得信賴。
我們都知道確實如此(畢竟這就是為什么會有編譯器、解釋器的原因),但我們卻常常不去考慮其潛在的含義。對于代碼生成器來說,需要手寫的重復而麻木的高級語言代碼,與機器碼一樣是可以批量生產(chǎn)的。當代碼生成器能夠提升抽象度時——即當生成器的說明性語句要比生成碼簡單時,使用代碼生成器會很合算,而生成代碼后就根本無需再費力地去手工處理了。
在Unix傳統(tǒng)中,人們大量使用代碼生成器使易于出錯的細節(jié)工作自動化。Parser/Lexer生成器就是其中的經(jīng)典例子,而makefile生成器和GUI界面式的構(gòu)建器(interface builder)則是新一代的例子。
(我們會在第9章討論這些技術(shù)。)
1.6.15 優(yōu)化原則: 雕琢前先得有原型,跑之前先學會走
原型設(shè)計最基本的原則最初來自于Kernighan 和 Plauger 所說的“90%的功能現(xiàn)在能實現(xiàn),比100%的功能永遠實現(xiàn)不了強”。做好原型設(shè)計可以幫助你避免為蠅頭小利而投入過多的時間。
由于略微不同的一些原因,Donald Knuth(程序設(shè)計領(lǐng)域中屈指可數(shù)的經(jīng)典著作之一《計算機程序設(shè)計藝術(shù)》的作者)廣為傳播普及了這樣的觀點:“過早優(yōu)化是萬惡之源”[9]。他是對的。
還不知道瓶頸所在就匆忙進行優(yōu)化,這可能是唯一一個比亂加功能更損害設(shè)計的錯誤。從畸形的代碼到雜亂無章的數(shù)據(jù)布局,犧牲透明性和簡潔性而片面追求速度、內(nèi)存或者磁盤使用的后果隨處可見。滋生無數(shù)bug,耗費以百萬計的人時——這點芝麻大的好處,遠不能抵消后續(xù)排錯所付出的代價。
經(jīng)常令人不安的是,過早的局部優(yōu)化實際上會妨礙全局優(yōu)化(從而降低整體性能)。在整體設(shè)計中可以帶來更多效益的修改常常會受到一個過早局部優(yōu)化的干擾,結(jié)果,出來的產(chǎn)品既性能低劣又代碼過于復雜。
在Unix世界里,有一個非常明確的悠久傳統(tǒng)(例證之一是Rob Pike以上的評論, 另一個是Ken Thompson關(guān)于窮舉法的格言):先制作原型,再精雕細琢。優(yōu)化之前先確保能用?;蛘撸合饶茏?,再學跑。“極限編程”宗師Kent Beck從另一種不同的文化將這一點有效地擴展為:先求運行,再求正確,最后求快。
所有這些話的實質(zhì)其實是一個意思:先給你的設(shè)計做個未優(yōu)化的、運行緩慢、很耗內(nèi)存但是正確的實現(xiàn),然后進行系統(tǒng)地調(diào)整,尋找那些可以通過犧牲最小的局部簡潔性而獲得較大性能提升的地方。
制作原型對于系統(tǒng)設(shè)計和優(yōu)化同樣重要——比起閱讀一個冗長的規(guī)格說明,判斷一個原型究竟是不是符合設(shè)想要容易得多。我記得Bellcore有一位開發(fā)經(jīng)理,他在人們還沒有談?wù)?ldquo;快速原型化”和“敏捷開發(fā)”前好幾年就反對所謂的“需求”文化。他從不提交冗長的規(guī)格說明,而是把一些shell腳本和awk代碼結(jié)合在一起,使其基本能夠完成所需要的任務(wù),然后告訴客戶派幾個職員來使用這些原型,問他們是否喜歡。如果喜歡,他就會說“在多少多少個月之后,花多少多少的錢就可以獲得一個商業(yè)版本”。他的估計往往很精確,但由于當時的文化,他還是輸給了那些相信需求分析應(yīng)該主導一切的同行。
—Mike Lesk
借助原型化找出哪些功能不必實現(xiàn),有助于對性能進行優(yōu)化;那些不用寫的代碼顯然無需優(yōu)化。目前,最強大的優(yōu)化工具恐怕就是delete鍵了。
我最有成效的一天就是扔掉了1000行代碼。
—Ken Thompson
(我們將在第12章對相關(guān)內(nèi)容進行深一步討論。)
1.6.16 多樣原則:決不相信所謂“不二法門”的斷言
即使最出色的軟件也常常會受限于設(shè)計者的想象力。沒有人能聰明到把所有東西都最優(yōu)化,也不可能預想到軟件所有可能的用途。設(shè)計一個僵化、封閉、不愿與外界溝通的軟件,簡直就是一種病態(tài)的傲慢。
因此, 對于軟件設(shè)計和實現(xiàn)來說,Unix傳統(tǒng)有一點很好,即從不相信任何所謂的“不二法門”。Unix奉行的是廣泛采用多種語言、開放的可擴展系統(tǒng)和用戶定制機制。
1.6.17 擴展原則: 設(shè)計著眼未來,未來總比預想快
如果說相信別人所宣稱的“不二法門”是不明智的話,那么堅信自己的設(shè)計是“不二法門”簡直就是愚蠢了。決不要認為自己找到了最終答案。因此,要為數(shù)據(jù)格式和代
碼留下擴展的空間,否則,就會發(fā)現(xiàn)自己常常被原先的不明智選擇捆住了手腳,因為你無法既要改變它們又要維持對原來的兼容性。
設(shè)計協(xié)議或是文件格式時,應(yīng)使其具有充分的自描述性以便可以擴展。一直,總是,要么包含進一個版本號,要么采用獨立、自描述的語句,按照可以隨時插入新的、換掉舊的而不會搞亂格式讀取代碼的方法組織格式。Unix經(jīng)驗告訴我們:稍微增加一點讓數(shù)據(jù)部署具有自描述性的開銷,就可以在無需破壞整體的情況下進行擴展,你的付出也就得到了成千倍的回報。
設(shè)計代碼時,要有很好的組織,讓將來的開發(fā)者增加新功能時無需拆毀或重建整個架構(gòu)。當然這個原則并不是說你能隨意增加根本用不上的功能,而是建議在編寫代碼時要考慮到將來的需要,使以后增加功能比較容易。程序接合部要靈活, 在代碼中加入“如果你需要……”的注釋。有義務(wù)給之后使用和維護自己編寫的代碼的人做點好事。
也許將來就是你自己來維護代碼,而在最近項目的壓力之下你很可能把這些代碼都遺忘了一半。所以,設(shè)計為將來著眼,節(jié)省的有可能就是自己的精力。
所有的Unix哲學濃縮為一條鐵律,那就是各地編程大師們奉為圭臬的“KISS”原則:
原文:http://www.linuxsong.org/2010/09/unix-philosophy/
【編輯推薦】