前言
Unix是開(kāi)源運(yùn)動(dòng)歷史上最著名的代表之一,而如今前端是開(kāi)源運(yùn)動(dòng)中最活躍的一支隊(duì)伍,我相信它們兩者之間一定有共通之處。讀懂此書,可以讓我們以后看待軟件開(kāi)發(fā)時(shí)角度變得不那么狹隘。
內(nèi)容
前面三章精要的概括了Unix的哲學(xué)思想、歷史發(fā)展、以及和其他操作系統(tǒng)的對(duì)比。
第一章:哲學(xué)
第一章開(kāi)門見(jiàn)山, 鞭辟入里地詮釋了Unix的靈魂所在:一門技術(shù)的編程藝術(shù)和設(shè)計(jì)哲學(xué)。
為什么它的生命力如此長(zhǎng)久?作者列舉了兩個(gè)原因:一是伴生的C語(yǔ)言的龐大影響力,二是帶目錄節(jié)點(diǎn)的樹(shù)形文件名字空間和用于通信的管道機(jī)制。
支持者認(rèn)為Unix有什么優(yōu)點(diǎn)呢?主要如下:
- 開(kāi)源軟件,同僚復(fù)審
- 跨平臺(tái)可移植性和開(kāi)放標(biāo)準(zhǔn)
- 與Internet的融合
- 開(kāi)源社區(qū)
- 簡(jiǎn)潔靈活
- 讓編程變得有趣
- 設(shè)計(jì)思想經(jīng)典,廣泛被借鑒
這些優(yōu)點(diǎn)同樣值得前端項(xiàng)目借鑒,大家想想jquery、vue、react、angular等是不是或多或少地符合以上幾點(diǎn)?
當(dāng)然Unix也不全是優(yōu)點(diǎn),它在商業(yè)上的不成功和追隨者的過(guò)于狂熱成了反對(duì)者的詬病之處。
另外在設(shè)計(jì)理念上,由機(jī)制而不是策略主導(dǎo)設(shè)計(jì),認(rèn)為最終用戶比設(shè)計(jì)人員更清楚自己想要什么也導(dǎo)致了一些問(wèn)題。不過(guò),時(shí)間證明,這才是保持生命力長(zhǎng)久的秘訣。
至于具體優(yōu)點(diǎn)有哪些,簡(jiǎn)直是羅列不過(guò)來(lái),四個(gè)大前提,五個(gè)原則,十七點(diǎn)小的概況。不過(guò)總的來(lái)說(shuō)就是開(kāi)發(fā)人員需要分清輕重緩急、懷疑一切,并以幽默樂(lè)觀的態(tài)度面對(duì)一切。一言蔽之:Keep It Simple,Stupid。
本章的最后描述了這些設(shè)計(jì)哲學(xué)是如何應(yīng)用在Unix中的,我們前端同樣也可以應(yīng)用,比如著名的前后端分離思想,協(xié)議文本化(http/json)等。
- 依賴轉(zhuǎn)置
- 數(shù)據(jù)流文本化
- 數(shù)據(jù)庫(kù)和應(yīng)用協(xié)議文本化
- 前后端分離
- 盡可能先寫原型
- 恰當(dāng)混用編程語(yǔ)言
- 寬收嚴(yán)發(fā)
- 不需要丟的信息絕對(duì)不丟
- 小就是美
- 追求軟件設(shè)計(jì)的卓越化
第二章:歷史
Unix的發(fā)展,作者使用了三重境界來(lái)描述,和王國(guó)維的“昨夜西風(fēng)凋碧樹(shù)。獨(dú)上高樓,望盡天涯路。衣帶漸寬終不悔,為伊消得人憔悴。眾里尋他千百度,驀然回首,那人卻在,燈火闌珊處。”三重境界頗為相似,值得深思。
簡(jiǎn)單評(píng)價(jià)一下Unix的歷史 :起于兼容分時(shí)系統(tǒng),以簡(jiǎn)單好用走遍江湖,廣納各高校貢獻(xiàn),雖失勢(shì)于商業(yè),終借Linux重獲新生,涅槃騰飛。
那推動(dòng)Unix發(fā)展的英雄們都是誰(shuí)呢?自然是hacker們,他們是計(jì)算機(jī)世界的江湖和俠客,雖然在社會(huì)和高校中各成一派,不過(guò)在經(jīng)歷過(guò)互聯(lián)網(wǎng)大融合(Unix和TCP/IP、ARPANET融合)和自由軟件運(yùn)動(dòng)后Unix文化已經(jīng)上升到一種意識(shí)形態(tài)的層面,協(xié)同開(kāi)發(fā)也成為一種趨勢(shì),分權(quán)、公開(kāi)、同僚復(fù)審的特點(diǎn)在其中體現(xiàn)的淋漓盡致,最終導(dǎo)致了開(kāi)源運(yùn)動(dòng)的興起。
老派Unix陣營(yíng)在商業(yè)上是吃過(guò)虧的,而開(kāi)源運(yùn)動(dòng)以一種更親和市場(chǎng)、更少對(duì)抗性的方式把軟件介紹給外部世界,從而也彌補(bǔ)了商業(yè)上的不足。
可以這樣說(shuō),軟件離開(kāi)源越近越繁榮,保持靈活性,別和低價(jià)而靈活的方案較勁,就可以得到長(zhǎng)久而旺盛的發(fā)展。
第三章:對(duì)比
這一章作者對(duì)比了Unix和其他操作系統(tǒng)的設(shè)計(jì)和編程習(xí)俗,雖然其中舉的很多例子已經(jīng)隨風(fēng)而逝了,但是其核心理念的對(duì)比還是非常具有參考價(jià)值的。
首先,作者對(duì)操作系統(tǒng)的風(fēng)格元素進(jìn)行了對(duì)比, 因?yàn)樗从沉瞬僮飨到y(tǒng)設(shè)計(jì)者的意圖,體現(xiàn)了成本和編程環(huán)境的限制對(duì)設(shè)計(jì)的均衡影響,更重要的是這種文化會(huì)隨機(jī)漂移,影響其他軟件。
具體又從下面幾個(gè)角度進(jìn)行詳細(xì)說(shuō)明:
- 統(tǒng)一性理念
Unix一切皆文件以及管道概念。
- 多任務(wù)能力
即多進(jìn)程并發(fā)能力,Unix有搶占式多任務(wù)能力。
其他操作系統(tǒng)有協(xié)作式多任務(wù)能力。
- 協(xié)作進(jìn)程
Unix具有低價(jià)的進(jìn)程生成成本、簡(jiǎn)便的IPC、以及能組合各種管道過(guò)濾器小工具,這就避免了多線程的坑,使系統(tǒng)的各個(gè)部分容易合作。
- 內(nèi)部邊界
以程序員最清楚一切作為準(zhǔn)繩,從而采取多用戶控制權(quán)限,在系統(tǒng)內(nèi)部設(shè)置三層內(nèi)部邊界:內(nèi)存管理、多用戶權(quán)限組、涉及安全的可信代碼塊。
- 文件屬性和記錄結(jié)構(gòu)
Unix沒(méi)有,因?yàn)橛X(jué)得記錄結(jié)構(gòu)是一種雞肋的存在。文件屬性可以幫助理解文件,但在面向字節(jié)流工具和管道的世界中會(huì)有語(yǔ)義問(wèn)題。
- 二進(jìn)制文件格式
Unix采用文本格式,而二進(jìn)制的兼容性和靈活性不好。
7.首選界面風(fēng)格
Unix采用CLI風(fēng)格,而其他操作系統(tǒng)有GUI風(fēng)格,雖然GUI或者CLI沒(méi)做好都有問(wèn)題,但CLI更易于程序員得到自己想要的東西。
- 目標(biāo)受眾
Unix為技術(shù)用戶而設(shè)計(jì),其他操作系統(tǒng)有為服務(wù)端、客戶端、最終用戶、單機(jī)、聯(lián)網(wǎng)等受眾設(shè)計(jì)的。
- 開(kāi)發(fā)的門檻
開(kāi)發(fā)門檻是由開(kāi)發(fā)工具的金錢成本、成為熟練開(kāi)發(fā)者的時(shí)間成本、甚至文化門檻組成,Unix支持輕松編程、玩家文化和精英編程文化。
- 具體操作系統(tǒng)比較
- VMS大全腫,商業(yè)領(lǐng)域尚可容忍
- MacOS的統(tǒng)一性理念是Mac界面方針,邊界脆弱,MacOS X融合Unix特點(diǎn)
- OS/2單用戶系統(tǒng),不靈活
- Windows辣雞但勝在商業(yè)
- BeOS深入線程化、專攻大量數(shù)據(jù)處理,死在商業(yè)
- MVS死板,用于大型機(jī)
- VM/CMS是Unix祖先,在大型機(jī)領(lǐng)域如魚得水
- Linux繼承Unix思想,更進(jìn)一步連接世界和人
最后作者總結(jié),種瓜得瓜,種豆得豆,一個(gè)好的操作系統(tǒng)必須可移植、支持網(wǎng)絡(luò)、把握客戶端的力量。從這里我突發(fā)奇想,會(huì)不會(huì)操作系統(tǒng)的未來(lái)就是瀏覽器呢?
設(shè)計(jì)思想
從第四章到第十三章都是展開(kāi)描述Unix各個(gè)設(shè)計(jì)思想的精髓,配有大量的例子,雖然這些例子大部分已經(jīng)湮沒(méi)在歷史的長(zhǎng)河里,不過(guò)對(duì)當(dāng)今的軟件開(kāi)發(fā)仍然具有重要借鑒意義。
第四章:模塊性(保持清晰,保持簡(jiǎn)潔)
隨著代碼的復(fù)雜度日益增加,我們需要使用子程序、庫(kù)、進(jìn)程等來(lái)劃分代碼。比如可以用定義清晰的接口把若干簡(jiǎn)單模塊組合起來(lái),這樣把程序劃分開(kāi)來(lái),并且讓它們易于協(xié)作,這是良好的設(shè)計(jì)方式。好的軟件滿足模塊化、正交性、緊湊性三個(gè)特點(diǎn)。
而模塊化最突出的特點(diǎn)就是封裝。模塊間通過(guò)一組嚴(yán)密、定義良好的程序調(diào)用和數(shù)據(jù)結(jié)構(gòu)來(lái)進(jìn)行通信。
有幾點(diǎn)需要注意:
- API是實(shí)現(xiàn)和設(shè)計(jì)間的滯塞點(diǎn)
- 簡(jiǎn)潔清晰的API表明設(shè)計(jì)是良好的
- 模塊不宜過(guò)多也不宜多少
模塊化另一處特點(diǎn)是緊湊性和正交性,一個(gè)軟件,設(shè)計(jì)和人類使用習(xí)慣相符就是緊湊的,任何操作沒(méi)有副作用就是正交的。
但緊湊意味著精煉,實(shí)際上的軟件基本上是半緊湊型的,因?yàn)槿绻幊陶咝枰洃浀臈l目數(shù)大于七說(shuō)明API不是緊湊型的。比如C和Python是半緊湊的、C++是反緊湊的,造成這種現(xiàn)象的原因是有時(shí)候?yàn)榱诵阅芎瓦m應(yīng)范圍等要犧牲緊湊性。
正交性代碼在源頭上減少了bug的可能性,更容易文檔化和復(fù)用,重構(gòu)根本的目標(biāo)就是提高正交性。
實(shí)踐當(dāng)中如何體現(xiàn)緊湊型和正交性呢?作者告訴我們需要遵循SPOT原則、圍繞強(qiáng)單一中心、時(shí)刻考慮分離的價(jià)值。
SPOT原則即真理的單點(diǎn)性,比如通過(guò)重構(gòu)去除重復(fù)代碼保留真理:
- 去除重復(fù)數(shù)據(jù)->讓代碼生成程序
- 去除重復(fù)代碼知識(shí)點(diǎn)->讓代碼生成部分文檔
- 去除重復(fù)頭文件和接口聲明->讓代碼生成頭文件和接口聲明
- 讓數(shù)據(jù)結(jié)構(gòu)“無(wú)垃圾,無(wú)混淆”
強(qiáng)單一中心即圍繞“解決一個(gè)定義明確的問(wèn)題”設(shè)計(jì)程序,例如diff算法、grep模式匹配、yacc生成語(yǔ)法解析器等都是專門為了解決某個(gè)問(wèn)題才出現(xiàn)的。
分離的價(jià)值指從零開(kāi)始,先去想盡量少的能做的事情,有點(diǎn)類似前端的漸進(jìn)增強(qiáng)思想。
在進(jìn)行模塊化之前,我們還需要考慮一個(gè)問(wèn)題:那就是軟件的層次。
大體可以分為自頂向下和自底向上的層次,自頂向下適用于精確、穩(wěn)定、底層自由的軟件,自底向上適用于探索性的編程,同時(shí)拋棄的代碼也比另外一種少。我們需要根據(jù)情況去設(shè)計(jì),要么抽象化細(xì)節(jié),要么圍繞某個(gè)模型組織代碼,而實(shí)際情況往往是結(jié)合兩種方式設(shè)計(jì)程序,這其中需要膠合層用來(lái)協(xié)調(diào)。
但膠合層要盡量薄,可以看做是分離原則的升華,C語(yǔ)言就是一個(gè)很好的膠合層。
膠合層的應(yīng)用之一就是各個(gè)共享庫(kù)。雖然理論上膠合層不應(yīng)存在,只有多變的策略和不變的機(jī)制。
另外作者討論了OO的思想,他指出OO只在某些領(lǐng)域適用,Unix程序員們大多是持懷疑態(tài)度的,因?yàn)槎哑龀橄髮邮呛芾廴说氖虑椋菀紫萑脒^(guò)度分層,而且過(guò)早設(shè)計(jì)喪失了優(yōu)化的機(jī)會(huì),以至于說(shuō):“過(guò)早優(yōu)化就是萬(wàn)惡之源。”
這章的最后,作者設(shè)計(jì)了幾個(gè)問(wèn)題供讀者自測(cè),看自己的代碼是否遵循了模塊化的原則:
- 全局變量有多少?
- 單個(gè)模塊的大小是否合適?
- 模塊內(nèi)的單個(gè)函數(shù)是否太大?
- 代碼是否有內(nèi)部API?
- API的入口是否超過(guò)七個(gè)?
- 模塊的入口點(diǎn)分布怎么樣?
第五章:文本化(好協(xié)議產(chǎn)生好實(shí)踐)
上一章講了如何把程序拆分成若干個(gè)模塊,這章就關(guān)于這些模塊間如何通信給出了答案。比如用于協(xié)作通信的應(yīng)用協(xié)議如何設(shè)計(jì),當(dāng)然還有一種文本化的應(yīng)用場(chǎng)景:數(shù)據(jù)存儲(chǔ),也會(huì)提到。
- 為了便于數(shù)據(jù)傳輸,通常需要序列化(列集)和反序列化(散集),比如前端中JSON的parse和stringify方法。
- 設(shè)計(jì)協(xié)議時(shí)需要考慮互用性、透明性、可拓展性和經(jīng)濟(jì)性。
- 性能不需要最先考慮,不然可能會(huì)是一種過(guò)早優(yōu)化。
- 數(shù)據(jù)文件和控制文件的信息流方向是不一樣的。
文本化最重要的特點(diǎn)是透明、可拓展。
比如Unix口令格式就是犧牲性能換透明和可拓展性的例子,.newsrc格式同樣是舍經(jīng)濟(jì)性而取透明性和可操作性。但也有適用于二進(jìn)制的情景,比如PNG格式。
隨后作者又道出了數(shù)據(jù)存儲(chǔ)文本化非常重要的一個(gè)特點(diǎn):擁有一套句法和詞法約定的數(shù)據(jù)文件元格式。具體給了幾個(gè)例子,JSON是我加上去的。
格式名 | 特點(diǎn) |
---|---|
DSV(分隔符分隔值) | 使用冒號(hào)分隔值,通過(guò)反斜杠轉(zhuǎn)義符處理特殊情況,和CSV形成鮮明對(duì)比 |
RFC 822 | 使用Tab或者Space來(lái)延續(xù),空行解釋為結(jié)束。應(yīng)用于郵件和HTTP協(xié)議,有多個(gè)記錄的時(shí)候邊界可能不明顯 |
Cookie-Jar | fortune使用的格式,使用%分隔,適用于結(jié)構(gòu)不易區(qū)別的文本段 |
Record-Jar | 前兩個(gè)格式的混合體,使用%%\n分隔,適用于可變字段的集合 |
XML | 類似于HTML、需要專門的解析器,適用于復(fù)雜的問(wèn)題(比如遞歸嵌套) |
Windows INI | 使用名稱-屬性對(duì)分隔,比較雞肋,復(fù)雜數(shù)據(jù)不及XML,簡(jiǎn)單不如DSV |
JSON | 使用鍵值對(duì)分隔,如今比XML流行 |
那Unix對(duì)文本化格式的約定是什么樣的呢?
- 如果可能,以新行符結(jié)束的每一行只存一個(gè)記錄。
- 如果可能,每行不超過(guò)80個(gè)字符。
- 使用#引入注釋。
- 支持反斜杠轉(zhuǎn)義約定。
- 在每行一條記錄的格式中,使用冒號(hào)或者任何連續(xù)的空白作為分隔符。
- 不要糾結(jié)tab和space。
- 優(yōu)先使用十六進(jìn)制而不是八進(jìn)制。
- 對(duì)于復(fù)雜的記錄,使用“節(jié)”,如果有多行,使用%%\n等作為分隔符。
- 節(jié)格式中,支持連續(xù)行。每行一個(gè)記錄字段或者用冒號(hào)終止字段名關(guān)鍵字作為引導(dǎo)字段。
- 格式可以自描述,不然就設(shè)立一個(gè)版本號(hào)。
- 注意浮點(diǎn)數(shù)取整問(wèn)題。
- 不要壓縮一部分。
前面花了這么多力氣描述數(shù)據(jù)存儲(chǔ)文本化,當(dāng)然是給描述協(xié)議文本化做鋪墊。因?yàn)檫m用于數(shù)據(jù)文件格式的好處同樣也適用于協(xié)議。
協(xié)議設(shè)計(jì)另外需要注意的點(diǎn)就是要牢記端對(duì)端設(shè)計(jì)原則,包括安全、認(rèn)證、性能。
同樣舉了例子,關(guān)于郵件協(xié)議的:
- SMTP發(fā)郵件,設(shè)計(jì)良好。
- POP3收郵件,和SMTP如出一轍。
- IMAP收郵件,希望取代POP3,但是加重了服務(wù)端的載荷減輕了客戶端的壓力。
最后話題一收,開(kāi)始闡述應(yīng)用協(xié)議元格式:文本格式、使用單行請(qǐng)求和響應(yīng),有效載荷數(shù)據(jù)多行,可以隨時(shí)拓展。
需要解決的問(wèn)題是簡(jiǎn)化網(wǎng)絡(luò)間事務(wù)處理的序列化操作,因?yàn)榫W(wǎng)絡(luò)的帶寬昂貴的多。比如HTTP,非常簡(jiǎn)單和通用,需要在安全和便利間做出抉擇,基于HTTP協(xié)議的其他協(xié)議很方便,但是缺點(diǎn)也很明顯:完全客戶端驅(qū)動(dòng),要額外接收HTTP報(bào)警信息。
還有其他的例子,比如BEEP使用二進(jìn)制包序列,且支持服務(wù)端推送信息,XML-RPC、SOAP、Jabber,XML...雖然它們已經(jīng)隨著歷史逐漸沒(méi)落了,但它們的優(yōu)點(diǎn)應(yīng)該會(huì)被未來(lái)的HTTP2吸收,到時(shí)候給我們一個(gè)更強(qiáng)大的HTTP。
第六章:透明性(來(lái)點(diǎn)光)
優(yōu)雅軟件的第三點(diǎn)特點(diǎn)是什么呢?可顯和透明性。
透明性指可以預(yù)測(cè)到程序行為的全部或者大部分情況,可顯性指幫助人們對(duì)軟件建立正確的“做什么、怎樣做”的觀念。
可以從用戶(UI)和程序員(代碼)的角度來(lái)看待可顯性和透明性。
舉幾個(gè)例子:
實(shí)例名 | 特點(diǎn) |
---|---|
audacity | 音頻軟件,UI透明 |
fetchmail | 郵件軟件,-v選項(xiàng)使得程序可顯,獲得“防彈程序”榮譽(yù) |
GCC | 編譯器,一系列處理階段都是為了可顯而設(shè)計(jì)的,例如dif |
kmail | 郵件程序,UI可顯透明,可以訪問(wèn)具體細(xì)節(jié)但是又不顯眼 |
SNG | 圖形文本轉(zhuǎn)換軟件,可顯但不是很透明 |
Terminfo | 數(shù)據(jù)庫(kù),可顯透明,使用文件系統(tǒng)作為數(shù)據(jù)庫(kù) |
Freeciv | 游戲,數(shù)據(jù)文件以文本格式編寫 |
那怎樣設(shè)計(jì)軟件才能實(shí)現(xiàn)透明性和可顯性呢?這就需要我們專注代碼同其他人交流的方式了。
- 不要疊放太多抽象層,增加透明性。讓其他人能夠預(yù)測(cè)程序行為。
- 編碼要求:調(diào)用深度要淺、代碼要有明顯的不變性、API正交、全局設(shè)置唯一的記錄器、實(shí)體一對(duì)一映射、容易找到給定部分、避免增加特殊情況、避免意義含糊的常量。
- 避免過(guò)度保護(hù),對(duì)于錯(cuò)誤調(diào)試應(yīng)該是透明的。
- 使用可編輯的表現(xiàn)形式:編寫文本化器或者瀏覽器。
- 便于故障診斷和故障恢復(fù),實(shí)現(xiàn)健壯性。
除此之外,還要為可維護(hù)性而設(shè)計(jì),維護(hù)性就是其他開(kāi)發(fā)者能夠順利地理解和修改軟件:
- 寧愿重構(gòu),都要拋棄辣雞代碼。
- 努力讓代碼成為活代碼而不是睡代碼和死代碼,吸引別人來(lái)開(kāi)發(fā)。
- 包含開(kāi)發(fā)者手冊(cè)比用戶手冊(cè)更有效。
第七章:多道程序設(shè)計(jì)(分離進(jìn)程為獨(dú)立的功能)
前面三章告訴我們軟件需要有模塊性、文本化、透明性、可顯性、可維護(hù)性。這一章講述的部分更加接近實(shí)現(xiàn):如何將大型程序分解成多個(gè)協(xié)作進(jìn)程,又稱多道程序設(shè)計(jì)。
首先,我們需要遵循做單件事并做好的原則,提倡分解程序。
有三種方法:降低進(jìn)程生成的開(kāi)銷、簡(jiǎn)化進(jìn)程間通信、使用簡(jiǎn)單透明的文本數(shù)據(jù)格式。
但協(xié)議真正的設(shè)計(jì)挑戰(zhàn)是協(xié)議邏輯,必須看得出很有表現(xiàn)力并可防范死鎖。
另外,不要過(guò)早關(guān)注性能,我們需要從性能調(diào)整中分離復(fù)雜度控制,它們是兩碼事。于是作者強(qiáng)烈建議我們盡量不要使用線程,還有模塊化可以達(dá)到更好的安全性。
接著作者介紹了Unix中的幾種IPC(進(jìn)程間通信)方法,從最簡(jiǎn)的逐步介紹到難的。
- 把任務(wù)轉(zhuǎn)給專門程序
最簡(jiǎn)單的程序協(xié)作方法。專門程序運(yùn)行時(shí)不需要和父進(jìn)程進(jìn)行交流。
例如mutt郵件用戶代理,將所有的編輯動(dòng)作統(tǒng)一到單獨(dú)的emacs進(jìn)程中。
- 管道、重定向和過(guò)濾
各種IPC方法的誕生的源泉,管道約定每個(gè)程序一開(kāi)始至少有兩個(gè)I/O數(shù)據(jù)流可用。
管道操作把一個(gè)程序的標(biāo)準(zhǔn)輸入連接到另一個(gè)程序的標(biāo)準(zhǔn)輸入,一系列管道被稱為管線,管道和重定向的組合威力很大,不需要單獨(dú)再寫一個(gè)完成某個(gè)功能的程序(類似鏈?zhǔn)秸{(diào)用),但它的缺點(diǎn)是單向性,不能讓數(shù)據(jù)雙向流動(dòng)。
實(shí)例是分頁(yè)程序的管道/制作單詞表、pic2graph、bc(1)和dc(1)、以及fetchmail為什么不用管線。
- 包裝器
它隱藏了shell管線的細(xì)節(jié),被調(diào)用程序?qū)S没?br> 實(shí)例是備份腳本,只需要傳參調(diào)用就好。
- 安全性包裝器和Bernstein鏈
它是高級(jí)版包裝器,需要先認(rèn)證再選擇性地調(diào)用。
實(shí)例是Bernstein鏈,每個(gè)繼發(fā)程序都取代了前一階段的程序,并且可以在程序鏈中插入另一個(gè)程序來(lái)修改系統(tǒng)的行為
- 從進(jìn)程
父子進(jìn)程通信,只在兩種情況下使用:兩者使用的協(xié)議不重要或者那個(gè)協(xié)議是根據(jù)第五章原則設(shè)計(jì)的。(前端的mv*的父子組件通信也是這樣的)
實(shí)例是scp和ssh,scp從ssh中獲取信息制作進(jìn)度條。
- 對(duì)等進(jìn)程間通信
對(duì)等通道,雙向數(shù)據(jù)流。(讓我想起了angular1.x的雙向數(shù)據(jù)綁定和其他前端框架的狀態(tài)管理)
種類 | 特點(diǎn) |
---|---|
臨時(shí)文件 | 簡(jiǎn)單,易沖突,有安全性風(fēng)險(xiǎn) |
信號(hào) | 一種軟中斷形式,信號(hào)間可能會(huì)競(jìng)爭(zhēng) |
系統(tǒng)守護(hù)程序和常規(guī)信號(hào) | 殺進(jìn)程信號(hào)和常規(guī)信號(hào) |
套接字 | 從封裝網(wǎng)絡(luò)數(shù)據(jù)訪問(wèn)的行為引申出來(lái),需要指定協(xié)議族(前后端分開(kāi)) |
共享內(nèi)存 | 最快,但是必須提供硬件,自己處理競(jìng)爭(zhēng)和死鎖 |
在使用這些IPC方法的時(shí)候,我們也需要注意一些問(wèn)題:
- 不要讓IPC和API耦合在一起
- 盡量別使用二進(jìn)制信息交換協(xié)議
- 避免混亂的流
- 避免遠(yuǎn)程過(guò)程調(diào)用,它雖然會(huì)提升性能但延遲高
- 別用線程(線程恐嚇),因?yàn)樽髡哒J(rèn)為它標(biāo)準(zhǔn)薄弱規(guī)范模糊
最后,我們?cè)谠O(shè)計(jì)層次上的進(jìn)程劃分無(wú)非就是完成程序在生命期內(nèi)交換數(shù)據(jù)的任務(wù)。
設(shè)計(jì)的過(guò)程中,我們需要想清楚:
- 程序前后端分離,何時(shí)建立通信?
- 何時(shí)何地完成信息的列集和散集?
- 何時(shí)產(chǎn)生緩沖問(wèn)題?
- 怎樣保證獲取信息的原子性?
第八章:微型語(yǔ)言(尋找歌唱的樂(lè)符)
語(yǔ)言本身越精簡(jiǎn),出bug的可能性越低,當(dāng)需要一個(gè)特定規(guī)則來(lái)完成任務(wù)時(shí),微型語(yǔ)言就誕生了。
它相對(duì)通用語(yǔ)言,體積小復(fù)雜度低。那什么情況下需要微型語(yǔ)言呢?當(dāng)預(yù)先認(rèn)識(shí)到設(shè)計(jì)一門微型語(yǔ)言,注意到規(guī)格說(shuō)明文件像微型語(yǔ)言,或者老是打補(bǔ)丁的時(shí)候。
然后讓我們理解一下語(yǔ)言分類法,由簡(jiǎn)單到復(fù)雜,是文件->微型語(yǔ)言->通用語(yǔ)言的。圖8.1很好地對(duì)當(dāng)時(shí)主要語(yǔ)言進(jìn)行了分類,它們的范疇從聲明型到命令型都有。
微型語(yǔ)言舉例
案例 | 特點(diǎn) |
---|---|
sng | 圖文轉(zhuǎn)換,便于用通用工具編輯圖片 |
regexp | 描述文本模型,簡(jiǎn)單地表達(dá)了識(shí)別行為 |
glade | 界面創(chuàng)建,專門生成XML代碼 |
m4 | 宏處理,便于把文本拓展成其他字符串,但要慎用 |
XSLT | 描述文本流變換,具有有限的類型分類和對(duì)外接口 |
DWB | 公式排版,聲明性語(yǔ)義 |
fetchmail的運(yùn)行控制語(yǔ)法 | 既有聲明性特點(diǎn)也有命令式特點(diǎn),有語(yǔ)法糖 |
awk | shell語(yǔ)言,既不緊湊也不通用所以被廢棄 |
PostScript | 專門用于設(shè)備排版,堆棧式語(yǔ)言 |
bc和dc | 命令性語(yǔ)言,任意精度。dc是逆波蘭標(biāo)記,bc是代數(shù)標(biāo)記 |
Emacs Lisp | 前端語(yǔ)言,可以描述編輯動(dòng)作 |
JS | 客戶端語(yǔ)言,逐漸變得通用 |
設(shè)計(jì)微型語(yǔ)言
當(dāng)需要把問(wèn)題說(shuō)明規(guī)格提升一個(gè)層次時(shí),且應(yīng)用領(lǐng)域的域原語(yǔ)簡(jiǎn)單而固定不變時(shí),我們就需要設(shè)計(jì)一門微型語(yǔ)言了。
- 選擇正確的復(fù)雜度
盡量簡(jiǎn)單,自頂向下設(shè)計(jì),符合美學(xué)。
了解嵌入型微型語(yǔ)言容易被濫用,宏的安全性差。
把語(yǔ)言一樣的特性加進(jìn)去作為事后補(bǔ)救措施是飲鴆止渴,一個(gè)偶爾圖靈完備的語(yǔ)言是其他開(kāi)發(fā)者的噩夢(mèng)。
- 擴(kuò)展和嵌入語(yǔ)言
通過(guò)別的語(yǔ)言擴(kuò)展或者嵌入進(jìn)別的語(yǔ)言。這對(duì)實(shí)現(xiàn)命令式語(yǔ)言來(lái)說(shuō)很好,但這個(gè)選擇決定于設(shè)計(jì)本身。
嵌入的話還需要考慮錯(cuò)誤語(yǔ)法檢查。
- 編寫自定義語(yǔ)法
參考XML作為語(yǔ)法基礎(chǔ)。
功能簡(jiǎn)單的微型語(yǔ)言就不要?dú)㈦u用牛刀了,遵循最小立異原則。
確實(shí)需要自定義語(yǔ)法的話使用yacc和lex幫助。
- 慎用宏
宏帶來(lái)的問(wèn)題大于好處,因?yàn)轭A(yù)處理器遇到表達(dá)式而不是期望的值的時(shí)候會(huì)發(fā)生意想不到的結(jié)果。并且宏難以閱讀和調(diào)試,擾亂了錯(cuò)誤診斷。
C預(yù)處理器有相應(yīng)的解決辦法,但m4沒(méi)有。
- 考慮清楚設(shè)計(jì)的是微型語(yǔ)言還是協(xié)議
考慮語(yǔ)言引擎是否可被其他程序交互調(diào)用,關(guān)鍵在于事務(wù)邊界的標(biāo)定程度。
第九章:生成(提升規(guī)格說(shuō)明的層次)
這章的核心觀點(diǎn)就只有一個(gè):把程序的邏輯轉(zhuǎn)移到數(shù)據(jù)中去。也就是數(shù)據(jù)驅(qū)動(dòng)編程,讓開(kāi)發(fā)者只關(guān)心數(shù)據(jù)結(jié)構(gòu)而不是代碼。
數(shù)據(jù)驅(qū)動(dòng)編程和OO的區(qū)別是數(shù)據(jù)實(shí)際上定義了程序的控制流,OO是封裝和固定的。數(shù)據(jù)驅(qū)動(dòng)和狀態(tài)機(jī)也是有區(qū)別的,一個(gè)是自動(dòng)生成代碼,一個(gè)是手工寫。
概括起來(lái)就是始終把問(wèn)題層次往上推。
案例 | 特點(diǎn) |
---|---|
ascii | 不維護(hù)代碼,只維護(hù)數(shù)據(jù) |
垃圾郵件統(tǒng)計(jì) | 統(tǒng)計(jì)數(shù)據(jù)比精巧的模式匹配奏效 |
fetchmailconf | 通過(guò)配置文件生成代碼 |
所以我們需要代碼生成代碼。建設(shè)性的懶惰是大師級(jí)程序員的美德。
案例 | 特點(diǎn) |
---|---|
生成ascii | 發(fā)布的源碼包含一個(gè)文件,里面是數(shù)據(jù),通過(guò)它們生成代碼 |
生成HTML | 數(shù)據(jù)+模板 |
第十章:配置(邁出正確的第一步)
配置是開(kāi)發(fā)前啟動(dòng)環(huán)境的重要步驟之一,另一個(gè)是交互通道。
什么可以配置呢?理論上一切可配置,但配置項(xiàng)太多會(huì)爆炸。(對(duì)新手不友好)
不如搞清楚什么不可配置:比如可以自動(dòng)檢測(cè)的東西。用戶也不應(yīng)該看到優(yōu)化開(kāi)關(guān),0.7秒以下藏起來(lái)。用其他程序能完成的任務(wù)就不要配置。
一般來(lái)說(shuō)可以在五個(gè)地方配置:運(yùn)行控制文件->系統(tǒng)環(huán)境變量->用戶運(yùn)行控制文件->用戶自定義環(huán)境變量->命令行選項(xiàng)。
這五個(gè)地方后面會(huì)覆蓋前面的,而且越到后面變化的幾率越大,好的Unix實(shí)踐要求使用同參數(shù)選項(xiàng)預(yù)期壽命最匹配的機(jī)制。
- 運(yùn)行控制文件
以rc結(jié)尾(比如eslintrc),存放與程序相關(guān)的聲明或命令,在程序啟動(dòng)時(shí)解析。
它分系統(tǒng)的和用戶的。語(yǔ)法有一套通用風(fēng)格,比如支持注釋、不區(qū)別空白符等。可以減少用戶閱讀和編輯時(shí)要接觸的新鮮事物,實(shí)例是.netrc文件:對(duì)用戶透明且遵循最小立異原則,但很難移植映射到其他操作系統(tǒng)里去。
- 環(huán)境變量
用來(lái)配置程序訪問(wèn)的環(huán)境。如搜索路徑、系統(tǒng)默認(rèn)值、uid、pid等關(guān)鍵信息。
也分系統(tǒng)的和用戶的。使用環(huán)境變量的時(shí)機(jī)是:變量值會(huì)根據(jù)上下文而變化或隨點(diǎn)文件不同而改變、不能以改變命令行調(diào)用來(lái)表述,操作系統(tǒng)間的移植同樣非常困難。
- 命令行選項(xiàng)
可以由腳本控制程序(如node腳本),一般以-開(kāi)頭,有Unix(推薦)、GNU、X toolkit風(fēng)格。
從-a到-z都被賦予了特殊的含義,某些大寫字母也是。盡一切辦法遵循最小立異原則和復(fù)用它們。
有命令行的地方就好移植。
那應(yīng)該如何挑選方法呢?根據(jù)從運(yùn)行控制文件->環(huán)境變量->命令行選項(xiàng),是最不易改變到最易改變的原則挑選。
因?yàn)楹笳邥?huì)覆蓋前者,并且依賴于程序在調(diào)用間隙需要保持多久的配置狀態(tài)。
實(shí)例是fetchmail,設(shè)置rc文件和環(huán)境變量,最后用命令行腳本化。
但描述的約定不是絕對(duì)的,當(dāng)明白自己想要什么并且想好了出錯(cuò)后怎么補(bǔ)救,可以讓收益大于代價(jià),是可以放手一搏的。
第十一章:接口(Unix環(huán)境下的用戶接口設(shè)計(jì)模式)
接口是程序和程序,程序和人類通訊的媒介。在設(shè)計(jì)時(shí)要遵循與其他程序通訊的前瞻性和最小立異原則。
比如I/O有三種方式:程序、IPC、已知文件或設(shè)備。所有存在的接口風(fēng)格,存在即合理。
那如何應(yīng)用最小立異原則來(lái)減少用戶學(xué)習(xí)負(fù)擔(dān)呢?
讓用戶對(duì)接口產(chǎn)生熟悉感,能共生和委派就弄,不能就效仿。
從Unix接口設(shè)計(jì)的歷史:打字機(jī)->命令行->可視化,我們可以知道Unix接口鼓勵(lì)機(jī)制而非策略。
接口設(shè)計(jì)評(píng)估有五種度量標(biāo)準(zhǔn):簡(jiǎn)潔、表現(xiàn)力、易用、透明和腳本化能力。
- 簡(jiǎn)潔是指操作起來(lái)容易程度
- 表現(xiàn)力指接口可以表現(xiàn)出沒(méi)有預(yù)見(jiàn)到的行為組合
- 易用指學(xué)習(xí)成本低
- 透明說(shuō)明用戶容易理解問(wèn)題域
- 腳本化是指接口能被其他程序使用
在接口發(fā)展歷史上,CLI和GNU接口之間的爭(zhēng)論一直存在,它們分別面向?qū)<壹?jí)用戶和初學(xué)者用戶,但我們需要權(quán)衡看待。
CLI表現(xiàn)力強(qiáng)、簡(jiǎn)潔、透明、腳本化但不易用,GNU表現(xiàn)力差、易用,其他不確定(看開(kāi)發(fā)者)。
比如計(jì)算器程序是一個(gè)很好的GNU程序,因?yàn)樗紤]到了用戶的未來(lái)行為,這是值得的。
不過(guò)Unix程序員的編程喜好還是透明、表現(xiàn)力和可配置,所以不用說(shuō),CLI是他們的最愛(ài)。
Unix接口模式
模式 | 特點(diǎn) |
---|---|
過(guò)濾器 | 標(biāo)準(zhǔn)輸入輸出,寬進(jìn)嚴(yán)出,不丟不增,例如sort |
Cantrip | 一次性,例如clear |
源 | 只出,例如ls |
接收器 | 只進(jìn),例如打印程序 |
編譯器 | 轉(zhuǎn)換信息,比如gcc |
ed | 有交互能力,例如gdb |
Roguelike | 比GNU效率高,但難以腳本化,例如Roguelike游戲 |
引擎和接口分離 | 機(jī)制策略分離,遵循MVC模式,有配置者/行動(dòng)者、假脫機(jī)/守護(hù)進(jìn)程、驅(qū)動(dòng)/引擎、C/S類型 |
CLI服務(wù)器 | 統(tǒng)一掌控程序啟動(dòng)服務(wù)器進(jìn)程,例如CLI服務(wù)器 |
基于語(yǔ)言的接口 | 使用微型語(yǔ)言來(lái)做專門的事,例如shell |
那我們非Unix程序員怎么應(yīng)用Unix接口模式呢?
答案是促進(jìn)腳本化和管道線能力,接口要盡量簡(jiǎn)單。
首先交互要分三種情況:和初級(jí)用戶、專家用戶、其他程序使用。
而且通常有幾種接口模式混合,首先封裝API邏輯,然后產(chǎn)生一個(gè)多價(jià)程序。
最重要的是cantrip、GUI、腳本接口模式,可選Roguelike模式。
然后作者闡述了網(wǎng)頁(yè)瀏覽器作為通用前端的好處,它統(tǒng)一了前端,讓前后端徹底分離。
優(yōu)點(diǎn)有很多,比如CGI公用網(wǎng)關(guān)接口和ajax助力前后端通信。缺點(diǎn)是網(wǎng)頁(yè)強(qiáng)迫以批處理風(fēng)格處理交互操作、使用無(wú)狀態(tài)協(xié)議管理持久會(huì)話。還有語(yǔ)言的兼容性成為一個(gè)問(wèn)題(現(xiàn)在已經(jīng)不是問(wèn)題,js統(tǒng)一了)。難以腳本化或?qū)⑹聞?wù)自動(dòng)化到后端:三層架構(gòu),前端->CGI->命令腳本。
最后作者告誡我們接口沒(méi)什么可說(shuō)的就閉嘴:遵循緘默原則。
因?yàn)闊o(wú)用信息會(huì)干擾合作、用戶、帶寬消耗,還有長(zhǎng)時(shí)間的操作要提供進(jìn)度條而不是廢話。確認(rèn)提示最好是“不”而不是“是”。
調(diào)試模式和開(kāi)發(fā)模式的消息可以區(qū)別對(duì)待。
第十二章:優(yōu)化
最有效的優(yōu)化是優(yōu)化之外的事情,例如清晰干凈的設(shè)計(jì)。
作者對(duì)于優(yōu)化這件事提出了自己的特有觀點(diǎn):最好啥也別做,時(shí)間會(huì)給我們答案。不寫代碼就沒(méi)有bug,這點(diǎn)真是無(wú)力反駁。
摩爾定律告訴我們付出得不到回報(bào),軟件優(yōu)化的那一點(diǎn)點(diǎn)很快就被硬件升級(jí)所抵消。要做也是做降維復(fù)雜度優(yōu)化而不是常數(shù)級(jí)的,比如從O(n^2)降到O(nlogn)
真要優(yōu)化的話,先估量再優(yōu)化,找到瓶頸再談優(yōu)化。
一般來(lái)說(shuō)造成瓶頸有三個(gè)原因:
- 工具誤差,根本性的問(wèn)題。檢測(cè)工具的代碼執(zhí)行有誤差,可以統(tǒng)計(jì)它們的調(diào)用次數(shù)。
- 外部延遲,也是根本的,不能隨機(jī)檢測(cè),要多次檢測(cè)。
- 過(guò)度調(diào)用,把子程序的時(shí)間開(kāi)銷算到了調(diào)用程序中。
所以衡量性能時(shí)不要只收集孤立的性能數(shù)字,更應(yīng)該綜合多個(gè)參數(shù),如問(wèn)題規(guī)模、CPU速度、磁盤速度等,最后建模得出結(jié)論。
還需要考慮非定域性(不確定性)之害,保持代碼短小簡(jiǎn)單,核心數(shù)據(jù)結(jié)構(gòu)必須留在最快的緩存里可以盡量避免。
某些優(yōu)化是不值得的,比如循環(huán)展開(kāi)。
緩存越大,緩存的開(kāi)銷越大,這點(diǎn)也需要注意。
本章后面一部分是針對(duì)協(xié)議優(yōu)化的內(nèi)容,關(guān)注點(diǎn)主要在吞吐量和延遲上。
總的來(lái)說(shuō)要設(shè)計(jì)出良好的網(wǎng)絡(luò)協(xié)議,需要盡量避免協(xié)議的往返。
實(shí)際上盡可能使用低的時(shí)延設(shè)計(jì)和忽略帶寬成本。作者提供了三種策略減少時(shí)延:對(duì)事務(wù)批處理、允許事務(wù)重疊、緩存。
1.批處理
先把更新累積起來(lái),最后一次性處理,比如DOM操作和DOMFragment。
2.重疊
將好幾條更新一起發(fā)送出去,阻塞和等待中間結(jié)果都是致命的,比如IMAP協(xié)議對(duì)請(qǐng)求做了標(biāo)記。
3.緩存
兼得魚和熊掌的策略,但必須考慮更新緩存的問(wèn)題,更新模式越復(fù)雜,bug越容易產(chǎn)生。
而且作者認(rèn)為緩存對(duì)于SPOT原則來(lái)說(shuō)是不好的,因?yàn)樗兇鉃榱诵阅軆?yōu)化。他建議轉(zhuǎn)用加速文件系統(tǒng)或者虛擬內(nèi)存實(shí)現(xiàn)會(huì)比緩存好。所以我們?cè)谑褂镁彺鏁r(shí)也應(yīng)該問(wèn)問(wèn)自己為什么要用緩存。
第十三章:復(fù)雜度(盡可能簡(jiǎn)單,但別簡(jiǎn)單過(guò)了頭)
真實(shí)世界的編程就是管理復(fù)雜度的問(wèn)題,我們應(yīng)盡量降低復(fù)雜度。
首先,我們需要理解復(fù)雜度是什么。作者分別從橫向縱向的角度進(jìn)行了比較。
橫向的復(fù)雜度有三個(gè)來(lái)源:程序員、用戶、代碼。
- 程序員-接口復(fù)雜度,可能會(huì)陷進(jìn)硬撐陷阱(極端晦澀的技法)。
- 用戶-實(shí)現(xiàn)復(fù)雜度,可能會(huì)造成人力尺度陷阱(將許多底層任務(wù)拋給用戶)。
- 代碼-代碼量,可能會(huì)陷入過(guò)專用陷阱(重復(fù)代碼)。
現(xiàn)實(shí)中可以對(duì)接口復(fù)雜度和實(shí)現(xiàn)復(fù)雜度折中,做出一方面是簡(jiǎn)潔的接口,一方面是便于傳播的簡(jiǎn)單軟件。
RG的文章認(rèn)為MIT哲學(xué)(簡(jiǎn)潔的接口)雖然讓軟件抽象地更好,但是New Jersey模型(簡(jiǎn)單軟件)更具傳播特性,這兩種方法的平衡就在于可以拿此換彼,例如404的出現(xiàn)。
縱向的復(fù)雜度有本質(zhì)的、選擇的和偶然的復(fù)雜度。
- 本質(zhì)的-有些問(wèn)題天生就是復(fù)雜的,比如設(shè)計(jì)火箭程序。
- 選擇的-由工程目標(biāo)決定。
- 偶然的-沒(méi)有找到實(shí)現(xiàn)規(guī)定功能集合的最簡(jiǎn)方法。
我們必須要注意選擇和偶然復(fù)雜度的區(qū)別,偶然的可以由良好的設(shè)計(jì)去除,選擇的只能改變工程目標(biāo)了。
映射復(fù)雜度
- 本質(zhì)接口復(fù)雜度通常無(wú)法去除,但可以調(diào)整代碼庫(kù)規(guī)模來(lái)減少代碼復(fù)雜度。
- 選擇復(fù)雜度邊界模糊,工程所涉及的任何方面都可能產(chǎn)生實(shí)現(xiàn)復(fù)雜度。
- 偶然復(fù)雜度可以通過(guò)良好的設(shè)計(jì)避免。
- 代碼復(fù)雜度可以采用更好的工具解決,實(shí)現(xiàn)復(fù)雜度可以選擇更好的算法解決,接口復(fù)雜度著眼于更好的交互設(shè)計(jì)。
- 處理復(fù)雜度依賴于見(jiàn)識(shí)而非方法。
有的時(shí)候由于本質(zhì)復(fù)雜度的存在,簡(jiǎn)潔不能勝任,我們只能保證功能,犧牲簡(jiǎn)潔了。
為了讓我們對(duì)復(fù)雜度的理解更加深刻,作者講了五個(gè)編輯器的故事。(字處理器不在討論范圍內(nèi),因?yàn)檫^(guò)于專用)
種類 | 特點(diǎn) |
---|---|
純文本 | 編輯器只知道其字節(jié)或者行結(jié)構(gòu) |
富文本 | 文本帶有屬性,如字體大小顏色等 |
句法感知 | 高亮,自動(dòng)縮進(jìn) |
批命令輸出解析 | 可以編譯并捕捉錯(cuò)誤 |
同輔助子進(jìn)程交互 | 可以調(diào)試/版本控制/和其他程序通信 |
編輯器 | 特點(diǎn) |
---|---|
ed | 純文本編輯 |
vi | 四不像 |
Sam | ed的進(jìn)化版,新增了功能 |
Emacs | 大而全 |
Wily | 鼠標(biāo)控制 |
由此作者總結(jié)了一下,編輯器的適當(dāng)規(guī)模應(yīng)該是什么樣的。
首先甄別它們的復(fù)雜度:ed最簡(jiǎn)單,Emacs最復(fù)雜,vi是折中派,Sam繼承了ed的簡(jiǎn)潔,Wily優(yōu)雅但有過(guò)于依賴鼠標(biāo)的代價(jià)。接著批判了vi:折中無(wú)用。(雖然現(xiàn)在vi還是很火)
不過(guò)最后一句話說(shuō)的還是好:少吃多干還是多吃多干取決于時(shí)代。(vscode應(yīng)該是多吃多干,sublime應(yīng)該是少吃多干)
結(jié)尾的時(shí)候引申到如何構(gòu)建軟件的適度規(guī)模:選擇需要管理的上下文環(huán)境,并且按照邊界所允許的最小化方式構(gòu)建程序。先證明其他方法行不通時(shí)再編寫大型程序。
具體實(shí)現(xiàn)
第十四章到第十六章介紹了Unix中涉及到的語(yǔ)言、工具以及輪子,我們前端也需要考慮自身領(lǐng)域的相關(guān)問(wèn)題,這對(duì)我們真正編碼的時(shí)候提升效率是非常有幫助的。
第十四章:語(yǔ)言(C還是非C)
Unix下的語(yǔ)言是豐饒的,并且鼓勵(lì)專門領(lǐng)域語(yǔ)言的設(shè)計(jì)。一方面,C語(yǔ)言是Unix的伴生語(yǔ)言,另一方面,各種腳本語(yǔ)言在動(dòng)態(tài)存儲(chǔ)管理的自動(dòng)化上有巨大優(yōu)勢(shì)。
C和Unix的關(guān)系是巧妙的,沒(méi)有Unix就沒(méi)有C,沒(méi)有C就沒(méi)有如今Unix文化的繁榮,C和C++取代了匯編語(yǔ)言在工業(yè)界的地位,重新掀起了一波技術(shù)浪潮。
雖然C和C++對(duì)要求極高的程序有意義,但損耗了程序員的精力。
隨之而來(lái)急劇下降的成本又改變了編程的經(jīng)濟(jì)含義。軟件的復(fù)雜化說(shuō)明自動(dòng)化內(nèi)存管理越來(lái)越重要,而且真正性能的損失往往來(lái)自外界。(網(wǎng)絡(luò)延遲、事件等待等)
到當(dāng)今這個(gè)年代,混合策略才有可能使得效率最大化。即一種在主語(yǔ)言中嵌入其他語(yǔ)言的策略。
比如可以嵌入內(nèi)存管理器完成內(nèi)存管理,嵌入腳本膠合邏輯。高級(jí)shell編程甚至可以自由混合語(yǔ)言編程。
接著作者對(duì)當(dāng)時(shí)的主流語(yǔ)言進(jìn)行了一番評(píng)估,因?yàn)槭煜ふZ(yǔ)言才能更好地使用和組合它們。
語(yǔ)言 | 特點(diǎn) |
---|---|
C | 資源效率最接近機(jī)器語(yǔ)言,但資源管理非常困難 |
C++ | 效率高,支持OO和泛型編程,但非常難用,鼓勵(lì)過(guò)于復(fù)雜的設(shè)計(jì) |
Shell | 完成小型任務(wù)自然快捷,但大型腳本必須依賴大量輔助命令造成兼容性問(wèn)題 |
Perl | 強(qiáng)大的工具語(yǔ)言以及正則匹配,但大型項(xiàng)目不優(yōu)雅、難以維護(hù) |
Tcl | 節(jié)儉緊湊的設(shè)計(jì)和作為解釋器語(yǔ)言的可拓展性,但數(shù)據(jù)結(jié)構(gòu)和命名空間等很怪異以至于難以用于大型項(xiàng)目 |
Python | 為嵌入而生的膠水語(yǔ)言,代碼清晰優(yōu)雅,但效率不高 |
Java | 自動(dòng)管理內(nèi)存并且支持OO,但設(shè)計(jì)的有些復(fù)雜而且沒(méi)達(dá)到一次編寫處處運(yùn)行的目的 |
Emacs Lisp | 結(jié)合了Lisp,優(yōu)雅、自動(dòng)管理內(nèi)存,但難以移植、性能差 |
作者還對(duì)這幾種語(yǔ)言的未來(lái)趨勢(shì)做了預(yù)測(cè):C/C++/Java不變、Tcl/Perl衰退、Python增長(zhǎng),事實(shí)證明他基本上是對(duì)的。
前面分析了那么多,該到自己動(dòng)手選擇編程工具包的時(shí)候了,因?yàn)镚UI工具包是會(huì)影響編程狀態(tài)的,而且某些語(yǔ)言和工具包的綁定有特定要求,比如Qt屹立不倒,但我用vsc。
第十五章:工具(開(kāi)發(fā)的技術(shù))
語(yǔ)言選好了,工欲善其事必先利其器,接著就是選工具了。
首先,我們需要一個(gè)對(duì)開(kāi)發(fā)者友好的操作系統(tǒng),像Unix就沒(méi)有固定的IDE,需要自己組合工具完成IDE的功能。這樣可以讓程序員更加專注于設(shè)計(jì),以編輯/編譯/調(diào)試為中心,其他細(xì)節(jié)用工具完成。前端也需要自己組合。
- 編輯器
作者主要對(duì)比了vi和Emacs,但我認(rèn)為vi類似于sublime,可以靈活拓展,Emacs類似于vsc,大而全,不過(guò)兩者兼用,用于不同的場(chǎng)景才是最佳策略。
- 專用代碼生成器
作者以lex和yacc這兩款生成語(yǔ)言詞法分析器的工具為例,介紹了lex是從輸入流中獲取標(biāo)記符號(hào),而yacc是解析一系列標(biāo)記符號(hào)來(lái)檢查是否符合語(yǔ)法。
但lex意外地被用于各種模式識(shí)別,輸入一堆,最后找出某個(gè)模式。
工具生成的代碼還是比手工正確高效。
- 自動(dòng)化編譯
把源碼進(jìn)行裝配打包發(fā)布才是最重要的,以make為例
它會(huì)尋找代碼間的依賴關(guān)系從而生成正確的打包版本(類似webpack),但要注意不能非常復(fù)雜,比如遞歸make。
一些腳本語(yǔ)言生成任務(wù)所需的文件也是很方便的,有all、test、clean、install等命令,類似npm。
makefile的可移植性和分析依賴能力靠幾個(gè)工具完成:makedepend、Imake、autoconf、automake等。
- 版本控制系統(tǒng)
為了追蹤變化,特別是bug,查看作者、時(shí)間、內(nèi)容等,我們需要版本控制系統(tǒng),而計(jì)算機(jī)更加擅長(zhǎng)這些細(xì)節(jié)。
手工版本控制隱性成本非常高,自動(dòng)化的版本控制能夠保存項(xiàng)目的歷史評(píng)注并避免修改沖突。
舉例為VCS,SVN是CVS的衍生版本(基于文件),GIT是現(xiàn)代版本控制系統(tǒng)(基于變化)。
- 運(yùn)行期調(diào)試工具
能夠打斷點(diǎn),檢查程序狀態(tài),可控執(zhí)行某個(gè)單一語(yǔ)句層次的部分,這是透明性設(shè)計(jì)的另一幫手。
- 性能分析工具
程序90%的執(zhí)行時(shí)間都耗費(fèi)在10%的代碼上,性能分析軟件幫助定位問(wèn)題,這樣就可以優(yōu)化關(guān)鍵的10%的代碼并遵循之前的優(yōu)化原則。
- 整合工具
編輯/編譯/測(cè)試/調(diào)試/版本控制...一體的工具,對(duì)前端來(lái)說(shuō)vsc+chrome可以完成95%的工作,不是IDE勝似IDE。
第十六章:重用(論不要重新發(fā)明輪子)
無(wú)為代碼,天下希及。這個(gè)無(wú)為是指最經(jīng)濟(jì)的行為,對(duì)無(wú)論是人員資本還是經(jīng)濟(jì)收益都有好處。
而讓代碼無(wú)為就是重用代碼,重用代碼又是避免發(fā)明輪子的最有效方法。Unix里里外外都支持重用,組合優(yōu)先于獨(dú)立。
作者還特意講了一個(gè)豬小兵的故事,這是千千萬(wàn)萬(wàn)程序員的縮影:
我們?cè)诠ぷ髦兄赜玫拇a可能會(huì)有問(wèn)題,不得逼我們重造一個(gè)輪子。但重用代碼是技術(shù)問(wèn)題、知識(shí)產(chǎn)權(quán)壁壘、行政問(wèn)題以及個(gè)人自我意識(shí)的綜合,所以代碼專用化還是開(kāi)放化讓程序員們糾結(jié)。
不過(guò),決定重用了,就必須透明。比如用源碼和注釋幫助使用者理解代碼,牢記只有變化才是永恒的,源碼可以延續(xù)而二進(jìn)制碼不行。
那重用和開(kāi)源的關(guān)系又是什么樣呢?作者說(shuō)開(kāi)源和重用就像愛(ài)情和繁殖的關(guān)系一樣,開(kāi)源也是為了重用自然而然發(fā)展而成保護(hù)透明性優(yōu)勢(shì)的策略。對(duì)開(kāi)發(fā)者來(lái)說(shuō),保證了經(jīng)驗(yàn)的價(jià)值,這也是職業(yè)發(fā)展的動(dòng)力。
開(kāi)放源碼是從意識(shí)形態(tài)上解決這些所有問(wèn)題的優(yōu)先方法。
另外一點(diǎn)是,開(kāi)源質(zhì)量通常大于閉源。因?yàn)橥袕?fù)議保證了標(biāo)準(zhǔn),評(píng)估開(kāi)源代碼的方法是閱讀其文檔和它的部分代碼,如果有一定年頭、反饋、協(xié)同作者數(shù)、社區(qū),這份代碼就是質(zhì)量高的。
去哪找呢?代碼庫(kù)和專用開(kāi)源網(wǎng)站。作者推薦了SourceForge、Freshmeat等網(wǎng)站,現(xiàn)在應(yīng)該是Github。
找到重用代碼就是節(jié)約自己的編碼時(shí)間,閱讀代碼的元數(shù)據(jù)并且試一試對(duì)自己是有好處的,并且閱讀代碼的細(xì)節(jié)也是為未來(lái)投資。
但使用開(kāi)源軟件還需要注意幾個(gè)問(wèn)題:考慮質(zhì)量、文檔、許可證。
文檔的話,專用文檔不如How To&FAQ等搜索來(lái)理解的快。
許可證相關(guān)的我們需要知道版權(quán)和許可證是兩碼事,誰(shuí)是版權(quán)所有者不重要,關(guān)鍵是許可證條款。它讓我們使用、修改代碼的權(quán)利有限制,標(biāo)準(zhǔn)許可證有MIT、BSD等,GPL帶有病毒性質(zhì),LGPL和MPL則削弱了這一點(diǎn)。另外記得,找律師只有1%的幫助...
社區(qū)的力量
最后幾章揭示了Unix為何生命力如此長(zhǎng)久的原因,在人和技術(shù)的平衡關(guān)系上做了非常仔細(xì)而微妙的分析。
第十七章:軟件可移植性與遵循標(biāo)準(zhǔn)
軟件開(kāi)源了,你想讓更多的人使用你的軟件,但是傳播的障礙常常來(lái)自操作系統(tǒng)和硬件結(jié)構(gòu)。
移植性一直是Unix的主要優(yōu)勢(shì),所以一旦設(shè)想軟件項(xiàng)目生命周期很短,就容易犯錯(cuò)。
只要在架構(gòu)、接口和實(shí)現(xiàn)上,API是穩(wěn)定的,其他特殊細(xì)節(jié)都是無(wú)關(guān)緊要的。
比如,C和Unix緊密關(guān)聯(lián),是硬件和操作系統(tǒng)間的薄膠合層。它是在1971年誕生的,后期逐步引入typedef、union等操作符,版本7引入了枚舉,并且將結(jié)構(gòu)體和union作為一等公民。C語(yǔ)言標(biāo)準(zhǔn)造成了“K&R C”和“ANSIC”的區(qū)別,并且產(chǎn)生了一個(gè)很好的實(shí)踐:在標(biāo)準(zhǔn)化之前,先實(shí)現(xiàn)各種pollify。
再延伸到Unix標(biāo)準(zhǔn),同樣使用公開(kāi)標(biāo)準(zhǔn)作為API說(shuō)明。雖然經(jīng)過(guò)了分裂和內(nèi)戰(zhàn),但Unix的標(biāo)準(zhǔn)在實(shí)踐中得以奠定下來(lái)。
開(kāi)源社區(qū)為了標(biāo)準(zhǔn)化,也需要確保源碼的兼容性很強(qiáng)。
舉個(gè)例子:IETF和RFC標(biāo)準(zhǔn)化過(guò)程,里面就體現(xiàn)了互聯(lián)網(wǎng)工程任務(wù)組的思維方式:標(biāo)準(zhǔn)必須來(lái)自于一個(gè)可用原型實(shí)現(xiàn)的經(jīng)驗(yàn)。
當(dāng)然也有理想化的標(biāo)準(zhǔn),比如臭名昭著的七層OSI模型。我們要考慮這點(diǎn):在成為標(biāo)準(zhǔn)之前,實(shí)現(xiàn)的要求是越來(lái)越高的。所以只有當(dāng)草案標(biāo)準(zhǔn)經(jīng)過(guò)了實(shí)現(xiàn)的廣泛測(cè)試并且達(dá)到了普遍接受的程度,就真正成為標(biāo)準(zhǔn)了。
對(duì)此作者打了個(gè)形象的比方:規(guī)格是DNA,代碼是RNA。因?yàn)榇a是可棄的,標(biāo)準(zhǔn)才是應(yīng)該保留完善的。
代碼從屬于標(biāo)準(zhǔn),先做一個(gè)原型再不斷地測(cè)試和演進(jìn)才是好辦法,生成半自動(dòng)化的測(cè)試套件也是一個(gè)主要優(yōu)勢(shì),可以穩(wěn)步迭代。至于相關(guān)的系統(tǒng)行為爭(zhēng)論可以在規(guī)則功能層面解決,非規(guī)格(功能)即bug。
話題順著到可移植性編程上,這個(gè)問(wèn)題看似是準(zhǔn)空間問(wèn)題,實(shí)際上時(shí)間上的持久性同樣重要。
首要問(wèn)題是選擇語(yǔ)言,作者對(duì)當(dāng)時(shí)流行的語(yǔ)言做了移植性分析:
語(yǔ)言 | 移植性特點(diǎn) |
---|---|
C | 高,但對(duì)于IPC、線程和GUI接口有困難 |
C++ | 類似C |
Shell | 差,大部分shell使用了其他可移植性差的工具 |
Perl | 良,看情況 |
Python | 優(yōu)秀 |
Tcl | 一般,隨項(xiàng)目復(fù)雜度有差異(看依賴) |
Java | 出色,但幾個(gè)版本間的GUI有兼容問(wèn)題 |
Emacs Lisp | 相當(dāng)好,問(wèn)題出在使用C接口的地方 |
總的來(lái)說(shuō)就是避免系統(tǒng)依賴性,發(fā)布源碼勝過(guò)二進(jìn)制碼,不要想著幫助不大的移植工具。
所以現(xiàn)在js成了可移植性語(yǔ)言之王。JavaScript is everywhere。
另外一點(diǎn)和移植化有關(guān)的是國(guó)際化,實(shí)現(xiàn)它我們需要分離信息庫(kù)和代碼,并且盡量使用UTF8字符集,使用正則時(shí)注意字符范圍就好了。
那可移植性/開(kāi)放標(biāo)準(zhǔn)和開(kāi)放源碼有什么關(guān)系呢?可移植性需要標(biāo)準(zhǔn),而開(kāi)源促進(jìn)了標(biāo)準(zhǔn)化。另外,不要依賴專有技術(shù),哪天作者跳坑就GG。
第十八章:文檔(向網(wǎng)絡(luò)世界闡釋代碼)
Unix最初的目的就是整理文檔,troff格式器是始祖,現(xiàn)在的趨勢(shì)是朝著html和url鏈接發(fā)展。
首先讓我們區(qū)分一下標(biāo)記型和可視型的文檔:一種是面向程序員的,一種是面向初級(jí)用戶的。
標(biāo)記型又分表現(xiàn)型和結(jié)構(gòu)型的,而大多數(shù)以標(biāo)記為中心的文檔系統(tǒng)都支持宏。
Unix風(fēng)格的文檔具備幾個(gè)文化和技術(shù)特征:
- 偏愛(ài)大文檔
- 寫給技術(shù)人員看,手冊(cè)頁(yè)往往包含一個(gè)BUGS部分
- 擅長(zhǎng)編寫參考書籍
各種Unix文檔格式
文檔類型 | 特征 |
---|---|
troff和DWT | 表示層語(yǔ)言不如結(jié)構(gòu)層語(yǔ)言,大量用于技術(shù)文檔 |
TEX | 使用輔助程序比如LETEX編寫,大量用于數(shù)學(xué)和科學(xué)領(lǐng)域 |
Texinfo | 可以生成HTML |
POD | Perl的標(biāo)記系統(tǒng),可以生成手冊(cè)但不能生成HTML |
HTML | 未來(lái)趨勢(shì),在生成索引上有問(wèn)題 |
DocBook | XML文檔類型定義,可以轉(zhuǎn)換成HTML、PDF等格式 |
于是書中大膽的預(yù)言未來(lái)的出路是XML一統(tǒng)天下...然而現(xiàn)在json橫空出世...
對(duì)于DocBook,作者還特意描述了一下:有一條轉(zhuǎn)換工具鏈,先驗(yàn)證是否是符合正確的文檔格式,再根據(jù)樣式單加樣式最后輸出。
但是最后還是批判了這條又臭又長(zhǎng)的工具鏈,即使優(yōu)化成FOP了還是不咋地。
最后本章總結(jié)了編寫文檔的最佳實(shí)踐:就是不要忽悠讀者。
- 數(shù)量多不會(huì)被認(rèn)為質(zhì)量高
- 信息密度要適中
- 大項(xiàng)目最好發(fā)布手冊(cè)頁(yè)/教程/常見(jiàn)問(wèn)題解答列表
- 文檔中要有readme
- 考慮新手用戶,技術(shù)名詞盡量用全稱
- 文檔格式應(yīng)該易于傳播
第十九章:開(kāi)源(在社區(qū)中編程)
Unix在開(kāi)放源碼上就做了很好的表率,將找/改bug的任務(wù)分解成多個(gè)并行的子任務(wù),然后眾力編程。
而開(kāi)源有如下幾個(gè)特點(diǎn):
- 源碼公開(kāi)
- 盡早發(fā)布/經(jīng)常發(fā)布,前提是項(xiàng)目正常運(yùn)行
- 給貢獻(xiàn)給予表?yè)P(yáng)
- 開(kāi)源項(xiàng)目管理盡量自動(dòng)化
之后便是本章的重點(diǎn):如何與開(kāi)源開(kāi)發(fā)者協(xié)同工作的最佳實(shí)踐
- 良好的修補(bǔ)實(shí)踐
1.1 是否換位思考/知道合并的后果
1.2 發(fā)送dif部分/針對(duì)當(dāng)前版本/不要包含可生成文件/不要發(fā)送系統(tǒng)自動(dòng)拓展的字段
1.3 在補(bǔ)丁中包含文檔/解釋/有用的注釋
通過(guò)代碼質(zhì)量評(píng)估補(bǔ)丁
良好的命名實(shí)踐
2.1 使用GNU風(fēng)格,例如foobar-1.2.3.tar。gz
2.2 文件名/版本和區(qū)分度是最重要的
2.3 尊重適當(dāng)?shù)谋镜丶s定
2.4 選擇容易鍵入的前綴良好的開(kāi)發(fā)實(shí)踐
3.1 不要依賴專有代碼
3.2 使用GNU自動(dòng)工具管理項(xiàng)目
3.3 先測(cè)試再發(fā)布代碼
3.4 發(fā)布前對(duì)代碼進(jìn)行健全檢查(能夠捕捉到錯(cuò)誤)
3.5 對(duì)readme進(jìn)行拼寫檢查
3.6 考慮移植性良好的發(fā)布制作實(shí)踐
4.1 確保打包文件總是解包到單一新目錄下
4.2 包含README文件(項(xiàng)目介紹、項(xiàng)目demo演示、環(huán)境問(wèn)題、關(guān)鍵架構(gòu)、編譯安裝指令、維護(hù)者光榮榜、項(xiàng)目新聞、項(xiàng)目郵件列表地址等)
4.3 尊重和遵從標(biāo)準(zhǔn)文件命名實(shí)踐(看社區(qū)的習(xí)俗)
4.4 為可升級(jí)性設(shè)計(jì)
4.5 提供RPM(類似npm)
4.6 提供校驗(yàn)和
供他人更好地下載、獲取和使用
- 良好的交流實(shí)踐
5.1 在社區(qū)和社交平臺(tái)發(fā)公告
5.2 建立一個(gè)網(wǎng)站
5.3 提供項(xiàng)目郵件列表
5.4 發(fā)布到主要的檔案站點(diǎn)
便于招攬用戶與合作者
本章最后分析了許可證如何挑選,畢竟它會(huì)對(duì)軟件施加限制。
雖然可以直接放在公共域,但使用某個(gè)標(biāo)準(zhǔn)許可證可以避免很多爭(zhēng)論。
有MIT、BSD、GPL等許可證,具體可以看阮一峰的如何選擇開(kāi)源許可證
終章:危機(jī)與機(jī)遇
本書的結(jié)尾章,總結(jié)了過(guò)去如何應(yīng)對(duì)的設(shè)計(jì)挑戰(zhàn),以及未來(lái)確定需要解決的問(wèn)題和有待開(kāi)拓的機(jī)會(huì)。
Unix最終要的是什么?當(dāng)然是它的文化,而從傳統(tǒng)來(lái)看它有平質(zhì)和偶然屬性,平質(zhì)屬性和偶然屬性是可以互相轉(zhuǎn)化的。
在歷史的長(zhǎng)河中,三個(gè)特殊的技術(shù)變化驅(qū)動(dòng)了Unix設(shè)計(jì)風(fēng)格中的重大變革:網(wǎng)絡(luò)互聯(lián)、位圖圖形顯示和PC普及。
在這其中Unix一直保持著獨(dú)有的設(shè)計(jì)準(zhǔn)則:模塊化、透明性、機(jī)制同策略分離等。
有人嘗試重做Unix(Plan9),但最終失敗,不過(guò)給予了Unix發(fā)展的啟迪。這是一個(gè)比Unix更Unix的設(shè)計(jì),并且還增加了一個(gè)概念:私有命名空間,但更優(yōu)秀解決方案的最危險(xiǎn)敵人,就是一個(gè)現(xiàn)存的、足夠優(yōu)秀的代碼庫(kù),沒(méi)有質(zhì)變,誰(shuí)會(huì)改變自己的慣性使用新事物呢?
當(dāng)然,Unix設(shè)計(jì)中也存在許多問(wèn)題,這里著重討論幾個(gè)存在爭(zhēng)論的失敗之處:
- Unix文件只有字節(jié)
- Unix對(duì)GUI的支持孱弱
- 文件刪除不可撤銷
- 假定文件系統(tǒng)是靜態(tài)的
- 作業(yè)控制設(shè)計(jì)拙劣
- API的異常處理不好
- 設(shè)備中插入鉤子的方法(ioctl和fcntl)是個(gè)雞肋
- 安全模型太過(guò)原始
- 名字種類太多
- 文件系統(tǒng)的爭(zhēng)論
- 朝向全局互聯(lián)網(wǎng)地址空間
跳出程序員的眼界,來(lái)看看整個(gè)社會(huì)環(huán)境下,Unix如何發(fā)展:
首先要獲得持續(xù)的經(jīng)濟(jì)支持,提高程序員的社會(huì)價(jià)值,然后組織終端用戶測(cè)試,獲取良好的反饋,最后要反對(duì)微軟/好萊塢等巨頭,為自由而斗爭(zhēng)。
Unix文化中也有問(wèn)題:內(nèi)部轉(zhuǎn)型的小問(wèn)題和克服歷史上的優(yōu)越感的大問(wèn)題。
比如和Mac之爭(zhēng),但Mac和Unix的設(shè)計(jì)哲學(xué)都有正確的一面,應(yīng)該互相理解。不要把自己從騎士變成惡龍。
舍得拋棄過(guò)去,不再過(guò)分依賴那些已經(jīng)很好地為我們工作過(guò)的設(shè)想。
勝利也不是全面的,低端市場(chǎng)和非技術(shù)用戶被忽略了。
最后的最后,作者語(yǔ)重心長(zhǎng)的說(shuō):
“我們能贏,只要我們想贏。“