首頁>技術>

前言

與人類社會的歷史相比,計算機的歷史非常短暫,上世紀五、六十年代都能稱為遠古時期了。但計算機的歷史又很神奇,早期的思想往往都很超前、很先進。比如EJB技術雖然是1998年提出的,但它的設計很超前,諸如微服務等後面出現的技術都或多或少借鑑了它的思想。通過了解計算機技術的發展歷史,往往能從中找到很多有創意的想法,能幫我們解決當下的問題。所以,今天想來掰扯一下Emacs和Vim這兩款經久不衰的老古董軟體的歷史八卦,看看有沒有值得借鑑的地方。

Vim的八卦

Vim族譜

首先從Vim編輯器的起源說起,下圖Vim的族譜:

Vim的前身是ed編輯器:

ed是UNIX系統上最古老的程式之一,從第一版本開始就入駐了,作者是Ken Thompson(UNIX作者之一)。它提供了面向行(Line)的基本編輯命令。ex是ed的超集,是Bill Joy(Sun公司創始人之一)在開發BSD時增強了ed,於是取名叫ex。但ex仍然是面向行的編輯器。Bill Joy後續又為ex提供了視覺化介面(Viusal Interface),提供全屏編輯能力,因此命名為vi。為了將vi移植到Amiga機器,Bram Moolenaar開發了Vi IMitation(Vi仿製品)。隨著功能的不斷增加,名字也升級為Vi IMproved(Vi改良版),即Vim。

ed編輯器

ed與VSCode、Sublime Text等現代編輯器有很大不同,如前文所說,它是一款編輯器(此處已幫大家劃重點),即編輯的物件是一整行文字

ed分命令模式與編輯模式。啟動ed後,預設進入命令模式,等待使用者輸入一條條命令。ed透過執行這些命令,最終達到編輯檔案的目的。使用Mac電腦的同學可以試試在終端裡執行ed。ed命令的格式是[定址][命令]:

定址:選中待操作的目標行。ed提供了三種定址方法:行號:從1開始的整數;$代表最後一行。模式:選中與正則表示式匹配的行。預設從當前行開始,選中第一個匹配的行。如/re/。新增字首g,則做全域性匹配。如g/re/範圍:由兩個地址組成的定址範圍,[地址],[地址]。如/BEGIN/,/END/命令:用單個字元表示。以下是最常用的命令:p:展示,輸出目標行。i:插入,將內容插入到目標行的上一行。a:追加,將內容追加到目標行的下一行。c:更改,替換目標行的內容。d:刪除,刪除目標行。s:替換,用正則表示式替換匹配行內容。

其中i、a、c命令會使ed從命令模式進入編輯模式,在編輯模式中輸入一行.則返回命令模式。以下是ed編輯的幾個示例:

刪除所有空行:g/^$/d。用字首g全域性搜尋正則表示式/^$/,並執行刪除命令。輸出所有包含“re”的行:g/re/p。同樣全域性搜尋正則表示式/re/,並執行展示命令。因為該功能實在太常用了,所以還特地開發了一個命令“grep”。

編輯器思維

如前文所說,ed編輯器與現代編輯器很不同,它其實是一個編輯命令直譯器;但ed編輯器又與現代編輯器很相同,所有編輯器的本質都是在不斷執行“定址”與“命令”,不同型別編輯器之間的差異只是編輯的物件不同:

ed是文字行編輯器:編輯的物件是文字行。Microsoft Word是文件編輯器:編輯的物件是章節、段落、詞句等文件元素。Sketch是圖形編輯器:編輯的物件是點、線、面等圖形元素。IntelliJ IDEA包含Java程式碼編輯器:編輯的物件是類、方法、語句等Java語義元素。jQuery是DOM編輯器:編輯的物件是DOM元素。先用CSS Selector定址,選中要處理的DOM元素;再用連綴表示式執行一系列編輯動作。……

由此可見,編輯器思維無處不在,只要符合“定址+命令”模式都可稱作編輯器,因此萬物皆可編輯!編輯器思維或編輯的本質,用開發者更熟悉的話術來講就是CRUD:

若以後有人質疑開發同學只是在做簡單的增刪改查,請勇敢地告訴他們:其實我是在做一個垂直領域的編輯器!

若意識到自己在做的其實是一個編輯器,就能利用編輯器思維快速發現系統能力的短板。以商品管理系統為例,若商品管理只提供透過ID查詢商品的功能,就猶如ed編輯器只支援用行號來定址一樣,使用就非常不方便,可以借鑑ed透過正則表示式的模式匹配定址能力,提供透過商品名稱等資訊來匹配商品、甚至透過商品照片來匹配相似商品的能力;類似的,建立商品能力也可以借鑑編輯器複製貼上的能力,提供用相似商品快速新建商品的能力,甚至還可以提供從其他平臺搬家的能力。

ed的族譜

前文只介紹了ed互動式編輯的功能,其實ed還支援指令碼化編輯,就是將輸入到終端的編輯命令儲存成一個指令碼檔案,供後續反覆執行。好處是可以用相同的編輯命令批次編輯任意多個檔案。

上圖是ed編輯器的族譜,後續的衍生程式都是選擇並增量了ed的部分能力。比如:

ex、vi、vim這條分支選擇了互動式路線。grep、fgrep、egrep選擇了模式匹配路線。sed、awk選擇了指令碼化路線。Emacs的八卦

從Vim陣營叛逃

我曾經是一名Vim重度使用者,因為在大學利用的作業系統是Debian Linux,無論是寫C程式碼還是Java程式碼,都是在Vim裡一把梭。好處是閉卷筆試時可以直接默寫,而用Eclipse的同學基本是記不住JDK API的全名。:-p

畢業後進了一家外企,不得不開始使用Windows XP系統,某天在記事本里寫東西,發現自己會經常無意識地按一下Esc鍵。用過Vim的同學肯定知道,這是在切換模式。這我意識到:Vim這種多模式的設計非常反人類。Vim啟動時預設進入的不是編輯模式,當新手使用者什麼都還沒學會時,他沒辦法把Vim當成普通的記事本來用。曾有一則關於Vim的笑話,說如何獲得一串隨機碼,答案是讓Vim新手嘗試退出Vim。

這種方式不符合我的口味,我嘗試去尋找新的編輯器——當什麼都沒學會的時候,可以當成最普通的記事本來用;當需要高階功能時,再透過快捷鍵等方式呼喚出來。結果發現Emacs恰好符合這個要求,所以從2010年開始我就從Vim陣營叛逃到了Emacs陣營。我認為這個使用方式的差異是Vim與Emacs最本質的區別:Vim會強迫使用者從一開始就按照它的規則來做事情;而Emacs則相對不需要過多前置知識。網路上曾流傳過一張編輯器的學習曲線,還蠻貼切的:

Emacs的起源

Vim的前身ed源自UNIX系統,而Emacs的前身TECO源自UNIX系統的前身——Multics系統。

上世紀70年代,GNU的創始人Richard Stallman在MIT的AI實驗室打工時,發明了TECO編輯器,執行在PDP-10機器上。與ed類似,TECO也是命令直譯器——接收並執行編輯命令——並且也採用單個字元作為命令名稱,比如“l”是移動一行,“5l”是移動5行。MIT那群大佬們想用TECO命令完成一些複雜的編輯工作,於是加入了分支判斷、迴圈等功能;但由於先天不足,TECO最開始設計的時候,沒有把命令設計成一套完備的程式語言,導致後續改進也很困難,比如命令名稱只能是單個字元,很快字元就不夠用了。

所謂基礎不牢地動山搖,大夥兒都認為需要用一套嚴謹完備的程式語言替代TECO的半成品指令碼語言。於是有一位叫Bernie的教授在Multics系統上用MacLisp重寫了TECO,並命名為Emacs,還為它寫了詳細的手冊,教大家如何擴充套件這個編輯器來滿足自己的工作需要。結果,這個版本的Emacs取得巨大成功,連Bernie的秘書——一個號稱自己不懂程式設計的人——都在照著手冊,有模有樣地寫Lisp程式碼來擴充套件編輯器功能。這件事兒在實驗室引起轟動後,Bernie為此做了一個總結:如果有一個應用——一個能幫你做點有用事情的程式——內嵌了Lisp,並且能透過Lisp程式擴充它的功能,對於學習程式設計而言,這是一種非常不錯的入門方式!那些自認為不會程式設計的人,這種方式會給他們編寫小但有用的程式的機會,讓他們在實踐中不斷成長,直到他們發現自己就是在程式設計。

Stallman他們覺得這個想法簡直屌炸天!同時他們想把這個好用的Emacs版本遷移到Multics系統之外的其他系統,但當時只有Multics系統上有完備的Lisp環境——既有編譯器又有直譯器——諸如UNIX等系統上都沒有。

這裡還有一個小插曲,Java之父James Gosling當年還寫了一個能跨平臺的Emacs版本,叫Gosmacs。本來社群想來一起完善這個版本,結果Gosling把它賣給了一家商業公司,同時它底層的Lisp不是一個真實完備的Lisp,而是一個叫Mocklisp的假Lisp,只是語法上和Lisp長得像而已。所以社群最終放棄了這個選項,決定從頭開始做一個全新的Emacs,也就是GNU Emacs。Stallman先用C語言開發一個跨平臺的Lisp直譯器——Emacs Lisp,再用Lisp實現編輯邏輯。這樣既能在所有平臺上用統一的Lisp方言來寫Emacs擴充套件,又能兼顧效能。

GNU Emacs有一段時間發展比較之後,因為Stallman自己一個人忙不過來,所以社群又建立了一個分支叫XEmacs,增強了字型抗鋸齒等功能。後來GNU Emacs的維護又變得積極了,把很多XEmacs的特性合併回GNU Emacs,所以現在XEmacs差不多是廢棄狀態,主流版本還是GNU Emacs。

系統設計

編輯器聖戰

程式設計師的世界裡充滿了鄙視鏈,有編輯器鄙視鏈、程式語言鄙視鏈、作業系統鄙視鏈……為什麼這些聖戰永遠打不完,到底是像《格列夫遊記》裡小人國因爭論剝雞蛋先打破大頭還是小頭而發動了戰爭,還是真的魚和熊掌不可兼得?

前文提到,Vim喜歡強迫使用者按照它的套路來做事。Vim從ed繼承了行編輯器的特性,底層模型是基於“行”的,所以會強行要求所有被編輯的物件適配成它的底層模型。你用Vim寫Java程式碼,你編輯的是文字行;你用Vim寫一篇部落格,你編輯的是文字行;你用Vim寫一篇論文,你編輯的還是文字行;無論你編輯的是類、函式、段落、目錄還是任何其他內容,都要先在腦海中翻譯成對應的dd、yy等面向行的編輯命令。

Emacs則是允許使用者先把Emacs改造成目標物件的個性化編輯器,能認識目標模型,比如段落、章節、目錄等。用一句時髦的話講就是Emacs有行業Know-How。同樣的例子:用Emacs寫Java程式碼,你編輯的是類、方法、語句……;你用Emacs寫一篇部落格,你編輯的是段落、句子……;你用Emacs寫一篇論文,你編輯的是目錄、章節、正文、索引……。

兩種設計方法

造成上述差異的原因是背後兩種不同的設計方法,分別稱作自頂向下(Top Down)與自底向上(Bottom Up):

方法自頂向下自底向上描述將大任務逐級拆分到顆粒度合適——足夠小、又能做些實際的事情——的小任務完善底層程式語言等——讓底層基建不斷逼近業務領域——來適應任務優點難度較低,目標明確,迭代快速功能完整,適應性強缺點與當前需求耦合過緊,應對變化能力稍弱難度較高,進展較慢

用Vim編輯屬於自頂向下方法——將編輯任務持續拆分,最終拆解到面向行的編輯命令;就像Java日常開發,會逐級拆分,最終拆解到JDK的API。用Emacs編輯屬於自底向上方法——先完善底層Emacs Lisp語言,逐步抽象出面向業務的領域特定語言,最終用DSL完成編輯任務;例如要編輯Markdown文件,就會提供諸如移動到下一個段落、下一個列表項、表格下一個單元等面向Markdown領域的特定編輯操作。

這兩種設計方法的差異並不意味著只是換個順序寫程式碼,而是系統抽象過程的差異,最終體現在系統擴充套件性的差異上。我個人把系統的可擴充套件分成4個等級:

硬編碼:系統執行時,資料和行為都已寫死,不能變化。可配置:系統執行時,資料可動態變化,但行為固定不變。可控制:系統執行時,資料可動態變化,並且由多種預定義的行為可供動態選擇。可程式設計:系統執行時,資料可動態變化,同時行為可在執行過程中動態新增,即使用者可重新系統行為。

自頂向下的極端是硬編碼,會過早地把功能限制在當前的需求裡,後來的需求只能儘量逼近初始模型;自底向上的極端是可程式設計,容易過渡設計,為未來不可能變化的場景提供靈活性,甚至會變成一門通用的程式語言。

兩種設計方法沒有絕對的對錯,都有各自適用的場景,單一地採用任何一種方法都會有問題,需要根據實際情況在快速實現和系統擴充套件性之間做權衡。也正因為沒有對錯之分,所以編輯器的聖戰永遠也打不完。

18
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 程式語言排行榜,java超python成第二名