讀《Unix編程藝術(shù)》小結(jié)

前言

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)呢?主要如下:

  1. 開(kāi)源軟件,同僚復(fù)審
  2. 跨平臺(tái)可移植性和開(kāi)放標(biāo)準(zhǔn)
  3. 與Internet的融合
  4. 開(kāi)源社區(qū)
  5. 簡(jiǎn)潔靈活
  6. 讓編程變得有趣
  7. 設(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ō)明:

  1. 統(tǒng)一性理念

Unix一切皆文件以及管道概念。

  1. 多任務(wù)能力

即多進(jìn)程并發(fā)能力,Unix有搶占式多任務(wù)能力。
其他操作系統(tǒng)有協(xié)作式多任務(wù)能力。

  1. 協(xié)作進(jìn)程

Unix具有低價(jià)的進(jìn)程生成成本、簡(jiǎn)便的IPC、以及能組合各種管道過(guò)濾器小工具,這就避免了多線程的坑,使系統(tǒng)的各個(gè)部分容易合作。

  1. 內(nèi)部邊界

以程序員最清楚一切作為準(zhǔn)繩,從而采取多用戶控制權(quán)限,在系統(tǒng)內(nèi)部設(shè)置三層內(nèi)部邊界:內(nèi)存管理、多用戶權(quán)限組、涉及安全的可信代碼塊。

  1. 文件屬性和記錄結(jié)構(gòu)

Unix沒(méi)有,因?yàn)橛X(jué)得記錄結(jié)構(gòu)是一種雞肋的存在。文件屬性可以幫助理解文件,但在面向字節(jié)流工具和管道的世界中會(huì)有語(yǔ)義問(wèn)題。

  1. 二進(jìn)制文件格式

Unix采用文本格式,而二進(jìn)制的兼容性和靈活性不好。

7.首選界面風(fēng)格

Unix采用CLI風(fēng)格,而其他操作系統(tǒng)有GUI風(fēng)格,雖然GUI或者CLI沒(méi)做好都有問(wèn)題,但CLI更易于程序員得到自己想要的東西。

  1. 目標(biāo)受眾

Unix為技術(shù)用戶而設(shè)計(jì),其他操作系統(tǒng)有為服務(wù)端、客戶端、最終用戶、單機(jī)、聯(lián)網(wǎng)等受眾設(shè)計(jì)的。

  1. 開(kāi)發(fā)的門檻

開(kāi)發(fā)門檻是由開(kāi)發(fā)工具的金錢成本、成為熟練開(kāi)發(fā)者的時(shí)間成本、甚至文化門檻組成,Unix支持輕松編程、玩家文化和精英編程文化。

  1. 具體操作系統(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)需要注意:

  1. API是實(shí)現(xiàn)和設(shè)計(jì)間的滯塞點(diǎn)
  2. 簡(jiǎn)潔清晰的API表明設(shè)計(jì)是良好的
  3. 模塊不宜過(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ù)代碼保留真理:

  1. 去除重復(fù)數(shù)據(jù)->讓代碼生成程序
  2. 去除重復(fù)代碼知識(shí)點(diǎn)->讓代碼生成部分文檔
  3. 去除重復(fù)頭文件和接口聲明->讓代碼生成頭文件和接口聲明
  4. 讓數(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è),看自己的代碼是否遵循了模塊化的原則:

  1. 全局變量有多少?
  2. 單個(gè)模塊的大小是否合適?
  3. 模塊內(nèi)的單個(gè)函數(shù)是否太大?
  4. 代碼是否有內(nèi)部API?
  5. API的入口是否超過(guò)七個(gè)?
  6. 模塊的入口點(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ì)提到。

  1. 為了便于數(shù)據(jù)傳輸,通常需要序列化(列集)和反序列化(散集),比如前端中JSON的parse和stringify方法。
  2. 設(shè)計(jì)協(xié)議時(shí)需要考慮互用性、透明性、可拓展性和經(jīng)濟(jì)性。
  3. 性能不需要最先考慮,不然可能會(huì)是一種過(guò)早優(yōu)化。
  4. 數(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ì)文本化格式的約定是什么樣的呢?

  1. 如果可能,以新行符結(jié)束的每一行只存一個(gè)記錄。
  2. 如果可能,每行不超過(guò)80個(gè)字符。
  3. 使用#引入注釋。
  4. 支持反斜杠轉(zhuǎn)義約定。
  5. 在每行一條記錄的格式中,使用冒號(hào)或者任何連續(xù)的空白作為分隔符。
  6. 不要糾結(jié)tab和space。
  7. 優(yōu)先使用十六進(jìn)制而不是八進(jìn)制。
  8. 對(duì)于復(fù)雜的記錄,使用“節(jié)”,如果有多行,使用%%\n等作為分隔符。
  9. 節(jié)格式中,支持連續(xù)行。每行一個(gè)記錄字段或者用冒號(hào)終止字段名關(guān)鍵字作為引導(dǎo)字段。
  10. 格式可以自描述,不然就設(shè)立一個(gè)版本號(hào)。
  11. 注意浮點(diǎn)數(shù)取整問(wèn)題。
  12. 不要壓縮一部分。

前面花了這么多力氣描述數(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)透明性和可顯性呢?這就需要我們專注代碼同其他人交流的方式了。

  1. 不要疊放太多抽象層,增加透明性。讓其他人能夠預(yù)測(cè)程序行為。
  2. 編碼要求:調(diào)用深度要淺、代碼要有明顯的不變性、API正交、全局設(shè)置唯一的記錄器、實(shí)體一對(duì)一映射、容易找到給定部分、避免增加特殊情況、避免意義含糊的常量。
  3. 避免過(guò)度保護(hù),對(duì)于錯(cuò)誤調(diào)試應(yīng)該是透明的。
  4. 使用可編輯的表現(xiàn)形式:編寫文本化器或者瀏覽器。
  5. 便于故障診斷和故障恢復(fù),實(shí)現(xiàn)健壯性。

除此之外,還要為可維護(hù)性而設(shè)計(jì),維護(hù)性就是其他開(kāi)發(fā)者能夠順利地理解和修改軟件:

  1. 寧愿重構(gòu),都要拋棄辣雞代碼。
  2. 努力讓代碼成為活代碼而不是睡代碼和死代碼,吸引別人來(lái)開(kāi)發(fā)。
  3. 包含開(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)的逐步介紹到難的。

  1. 把任務(wù)轉(zhuǎn)給專門程序

最簡(jiǎn)單的程序協(xié)作方法。專門程序運(yùn)行時(shí)不需要和父進(jìn)程進(jìn)行交流。
例如mutt郵件用戶代理,將所有的編輯動(dòng)作統(tǒng)一到單獨(dú)的emacs進(jìn)程中。

  1. 管道、重定向和過(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為什么不用管線。

  1. 包裝器

它隱藏了shell管線的細(xì)節(jié),被調(diào)用程序?qū)S没?br> 實(shí)例是備份腳本,只需要傳參調(diào)用就好。

  1. 安全性包裝器和Bernstein鏈

它是高級(jí)版包裝器,需要先認(rèn)證再選擇性地調(diào)用。
實(shí)例是Bernstein鏈,每個(gè)繼發(fā)程序都取代了前一階段的程序,并且可以在程序鏈中插入另一個(gè)程序來(lái)修改系統(tǒng)的行為

  1. 從進(jìn)程

父子進(jìn)程通信,只在兩種情況下使用:兩者使用的協(xié)議不重要或者那個(gè)協(xié)議是根據(jù)第五章原則設(shè)計(jì)的。(前端的mv*的父子組件通信也是這樣的)
實(shí)例是scp和ssh,scp從ssh中獲取信息制作進(jìn)度條。

  1. 對(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)題:

  1. 不要讓IPC和API耦合在一起
  2. 盡量別使用二進(jìn)制信息交換協(xié)議
  3. 避免混亂的流
  4. 避免遠(yuǎn)程過(guò)程調(diào)用,它雖然會(huì)提升性能但延遲高
  5. 別用線程(線程恐嚇),因?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ò)程中,我們需要想清楚:

  1. 程序前后端分離,何時(shí)建立通信?
  2. 何時(shí)何地完成信息的列集和散集?
  3. 何時(shí)產(chǎn)生緩沖問(wèn)題?
  4. 怎樣保證獲取信息的原子性?

第八章:微型語(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ǔ)言了。

  1. 選擇正確的復(fù)雜度

盡量簡(jiǎn)單,自頂向下設(shè)計(jì),符合美學(xué)。
了解嵌入型微型語(yǔ)言容易被濫用,宏的安全性差。
把語(yǔ)言一樣的特性加進(jìn)去作為事后補(bǔ)救措施是飲鴆止渴,一個(gè)偶爾圖靈完備的語(yǔ)言是其他開(kāi)發(fā)者的噩夢(mèng)。

  1. 擴(kuò)展和嵌入語(yǔ)言

通過(guò)別的語(yǔ)言擴(kuò)展或者嵌入進(jìn)別的語(yǔ)言。這對(duì)實(shí)現(xiàn)命令式語(yǔ)言來(lái)說(shuō)很好,但這個(gè)選擇決定于設(shè)計(jì)本身。
嵌入的話還需要考慮錯(cuò)誤語(yǔ)法檢查。

  1. 編寫自定義語(yǔ)法

參考XML作為語(yǔ)法基礎(chǔ)。
功能簡(jiǎn)單的微型語(yǔ)言就不要?dú)㈦u用牛刀了,遵循最小立異原則。
確實(shí)需要自定義語(yǔ)法的話使用yacc和lex幫助。

  1. 慎用宏

宏帶來(lái)的問(wèn)題大于好處,因?yàn)轭A(yù)處理器遇到表達(dá)式而不是期望的值的時(shí)候會(huì)發(fā)生意想不到的結(jié)果。并且宏難以閱讀和調(diào)試,擾亂了錯(cuò)誤診斷。
C預(yù)處理器有相應(yīng)的解決辦法,但m4沒(méi)有。

  1. 考慮清楚設(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ī)制。

  1. 運(yùn)行控制文件

以rc結(jié)尾(比如eslintrc),存放與程序相關(guān)的聲明或命令,在程序啟動(dòng)時(shí)解析。
它分系統(tǒng)的和用戶的。語(yǔ)法有一套通用風(fēng)格,比如支持注釋、不區(qū)別空白符等。可以減少用戶閱讀和編輯時(shí)要接觸的新鮮事物,實(shí)例是.netrc文件:對(duì)用戶透明且遵循最小立異原則,但很難移植映射到其他操作系統(tǒng)里去。

  1. 環(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)間的移植同樣非常困難。

  1. 命令行選項(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è)原因:

  1. 工具誤差,根本性的問(wèn)題。檢測(cè)工具的代碼執(zhí)行有誤差,可以統(tǒng)計(jì)它們的調(diào)用次數(shù)。
  2. 外部延遲,也是根本的,不能隨機(jī)檢測(cè),要多次檢測(cè)。
  3. 過(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)源:程序員、用戶、代碼。

  1. 程序員-接口復(fù)雜度,可能會(huì)陷進(jìn)硬撐陷阱(極端晦澀的技法)。
  2. 用戶-實(shí)現(xiàn)復(fù)雜度,可能會(huì)造成人力尺度陷阱(將許多底層任務(wù)拋給用戶)。
  3. 代碼-代碼量,可能會(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ù)雜度。

  1. 本質(zhì)的-有些問(wèn)題天生就是復(fù)雜的,比如設(shè)計(jì)火箭程序。
  2. 選擇的-由工程目標(biāo)決定。
  3. 偶然的-沒(méi)有找到實(shí)現(xiàn)規(guī)定功能集合的最簡(jiǎn)方法。

我們必須要注意選擇和偶然復(fù)雜度的區(qū)別,偶然的可以由良好的設(shè)計(jì)去除,選擇的只能改變工程目標(biāo)了。

映射復(fù)雜度

復(fù)雜度.png
  • 本質(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é)用工具完成。前端也需要自己組合。

  1. 編輯器

作者主要對(duì)比了vi和Emacs,但我認(rèn)為vi類似于sublime,可以靈活拓展,Emacs類似于vsc,大而全,不過(guò)兩者兼用,用于不同的場(chǎng)景才是最佳策略。

  1. 專用代碼生成器

作者以lex和yacc這兩款生成語(yǔ)言詞法分析器的工具為例,介紹了lex是從輸入流中獲取標(biāo)記符號(hào),而yacc是解析一系列標(biāo)記符號(hào)來(lái)檢查是否符合語(yǔ)法。
但lex意外地被用于各種模式識(shí)別,輸入一堆,最后找出某個(gè)模式。
工具生成的代碼還是比手工正確高效。

  1. 自動(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等。

  1. 版本控制系統(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)(基于變化)。

  1. 運(yùn)行期調(diào)試工具

能夠打斷點(diǎn),檢查程序狀態(tài),可控執(zhí)行某個(gè)單一語(yǔ)句層次的部分,這是透明性設(shè)計(jì)的另一幫手。

  1. 性能分析工具

程序90%的執(zhí)行時(shí)間都耗費(fèi)在10%的代碼上,性能分析軟件幫助定位問(wèn)題,這樣就可以優(yōu)化關(guān)鍵的10%的代碼并遵循之前的優(yōu)化原則。

  1. 整合工具

編輯/編譯/測(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ù)特征:

  1. 偏愛(ài)大文檔
  2. 寫給技術(shù)人員看,手冊(cè)頁(yè)往往包含一個(gè)BUGS部分
  3. 擅長(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):

  1. 源碼公開(kāi)
  2. 盡早發(fā)布/經(jīng)常發(fā)布,前提是項(xiàng)目正常運(yùn)行
  3. 給貢獻(xiàn)給予表?yè)P(yáng)
  4. 開(kāi)源項(xiàng)目管理盡量自動(dòng)化

之后便是本章的重點(diǎn):如何與開(kāi)源開(kāi)發(fā)者協(xié)同工作的最佳實(shí)踐

  1. 良好的修補(bǔ)實(shí)踐
    1.1 是否換位思考/知道合并的后果
    1.2 發(fā)送dif部分/針對(duì)當(dāng)前版本/不要包含可生成文件/不要發(fā)送系統(tǒng)自動(dòng)拓展的字段
    1.3 在補(bǔ)丁中包含文檔/解釋/有用的注釋

通過(guò)代碼質(zhì)量評(píng)估補(bǔ)丁

  1. 良好的命名實(shí)踐
    2.1 使用GNU風(fēng)格,例如foobar-1.2.3.tar。gz
    2.2 文件名/版本和區(qū)分度是最重要的
    2.3 尊重適當(dāng)?shù)谋镜丶s定
    2.4 選擇容易鍵入的前綴

  2. 良好的開(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 考慮移植性

  3. 良好的發(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)和

供他人更好地下載、獲取和使用

  1. 良好的交流實(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ō):

“我們能贏,只要我們想贏。“

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,143評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 175,553評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,620評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,416評(píng)論 6 405
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 54,940評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,170評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,709評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,597評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評(píng)論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,029評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,407評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,663評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,403評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,746評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容