代碼結構的演進
過年了,各種公眾號都在玩拜年,玩紅包,甚至在玩喜羊羊,連程序君訂閱的一些技術類的公號也不能免俗。作為大年三十還在苦逼上班的程序君,自然不會放過這個絕好的機會寫點和技術沾邊的文章來填補大家空虛的心靈 —— 因為我深深的知道,你們會邊看春晚邊想這個歌舞是不是冗餘代碼,那個小品是不是UT沒做好,主持人雖然使用了多核,但使用的腳本一看就是php,性能不佳;來年應該讓導演上erlang。。。
為了讓你們那無時無刻不在運算的大腦繼續保持很好的節奏感,這篇文章依舊會和技術有關;但又不會太techie,看一看,想一想,樂一樂就好。
今天瞎侃侃代碼的發展史,走心走腎隨你。
言歸正傳。
縱觀整個軟體工程的發展史,項目代碼規模的不斷增大導致了人們一直在尋求更好的代碼組織方式,使其適應「笨拙」的人腦的理解能力。
最早的代碼估計沒有項目的概念,只是一個文件,幾張A4紙就能將其表述清楚。這時的代碼有最原始的控制結構(jmp,goto),整個程序揉在一起,被形象地稱作義大利麵條(spaghetti)。
義大利麵條式的代碼撐不到太大的規模,便超出了人腦所能理解的範疇。為了讓代碼更可讀,從goto中衍生出了更好的控制邏輯:分支,循環(或者遞歸),以及用於管理目的的 [1] 函數,類 [2] 和模塊。代碼可以以更清晰,更可控地方式被撰寫。
感謝文件系統的誕生,原本處在一個平面上的代碼被人們以樹狀的結構進行管理。功能不相干的代碼被放入不同的文件,繼而放在不同的目錄,於是庫或者模塊的概念產生了。有的程序員開始專門為其他程序員開發庫或者模塊。代碼脫離了小農時代的自給自足,進化到了商品經濟時代的社會化分工。
有了社會化分工,代碼的規模開始急劇擴張。為了讓規模化的代碼編譯過程更可控,誕生了make,scons,grandle,mix等工具;為了讓代碼的修改和追蹤更可控,cvs,svn,git等工具又前來救駕。
有了分工,便有了依賴,依賴管理工具便就此誕生。pip,ivy,npm等在其各自的領域中,為開發人員減輕想想就頭疼的依賴衝突。
軟體工程越大,boilerplate代碼就越多。各種framework對此專門優化,把boilerplate扼殺在搖籃里。DSL和metaprogramming讓代碼越來越專註於business logic,其它一切都「放著我來」。
從撰寫的角度來講,隨著各種語言的lisp化,以及主流VM上lisp的親戚 [3] 越來越成熟,代碼的靜態可維護性已經不成問題。然而,運行中的代碼卻依然沒有太大的改觀。大部分軟體,儘管從靜態的角度來看,模塊化和關注點分離已經做到了足夠好,代碼與代碼之間甚至在物理上都被樹狀的文件系統隔離,可當其編譯運行起來成一個進程後,這種隔離消失了,所有的運行的代碼又被統統揉在了一個平面中。在這個平面上,某個角落裡產生的exception,有可能把整個進程搞掛。所以為了儘可能讓某個局部的錯誤不至於影響全局,大家一致的做法是defensive coding —— 甭管誰的代碼引起的問題,反正問題不能出在我這裡,try catch也好,if error check也好,總之寧可錯殺千人,不可漏掉一個。
defensive coding不能解決所有問題,那麼,運行時有什麼類似於文件系統的東西把不同邏輯的代碼隔離開來,當defensive coding無法奏效,也可以把exception控制在可控的範圍?
threading/multiprocessing可以勉強看作是一種手段,儘管它們的初衷是為了concurrency。不少軟體利用multiprocessing,使用經典的supervisor/worker模式(如nginx),把更易出錯的worker的exception隔離開來,讓軟體的robust大大提高。
可程序員們還在呼喚更好的解決之道:既然靜態的代碼可以用樹狀的層級結構來管理,為什麼運行時的代碼不能採用同樣的方式呢?
大家的目光停留到了erlang,這個誕生於上世紀80年代,靜靜躺在不為人知的角落裡的語言。它有一種奇怪的結構叫process(下面稱actor,避免和眾所周知的process混淆),還有一種奇怪的思想叫let it crash。
在erlang中,actor則相當於軟體的細胞。若干個細胞結合起來,成為軟體的組織;若干組織結合起來,成為軟體的器官,然後再結合成整套軟體。這種軟體的組合模式看上去像是這樣:
如果某個細胞損傷,那就讓這個細胞死去,再克隆出一個新的細胞即可,這就是let it crash的思想。細胞如此,由細胞組成的組織,或者器官,也如此。erlang的actor發生異常,如果自己搞不定,就把自己殺掉,由其supervisor重新構建。
(restart one for one)
(restart one for all)
(restart rest for one)
生物體總有些關鍵的部位是不能損壞的(如心臟,大腦),一旦損壞,生物體也就完蛋了。這部分要保證足夠健壯,不會出問題。在erlang里,這被稱為error kernel。軟體的撰寫者要分辨出軟體的哪部分是一定不能出問題的,一旦出問題,軟體就得crash。除此之外,軟體的任何部分(actor)出問題,無非就是這個部分重啟的事情。
(error kernel)
於是,運行時的軟體不再是一個各種代碼揉在一起的平面,而是一棵層層隔離的樹。
erlang開啟的先河,被scala吸收了過去,構建在JVM和scala之上的akka將這思想傳播到了更深遠的地方 footnote: [不是說akka優於erlang - akka還在拾erlang牙慧的路上 - 只是JVM過去二十年在企業的應用要遠遠廣於beam],並且做得更徹底一些(erlang的actor可以選擇是否supervise,akka所有actor都會被parent supervise)。
也許未來十年,這將成為軟體的主要組織方式 [4]。因為,「永遠在線」的互聯網,越來越承受不起宕機 —— 哪怕僅僅是幾分鐘。未來,也許軟體需要達到硬體一樣的6sigma,儘管這在目前來講簡直是天方夜譚 [5]。
你覺得呢?祝大家農曆新年快樂,事事如意,人人都有大紅包!如果您覺得這篇文章不錯,請點贊。多謝!
歡迎訂閱公眾號『程序人生』(搜索微信號 programmer_life)。每篇文章都力求原汁原味,北京時間中午12點左右,美西時間下午8點左右與您相會。
1. Don』t Repeat Yourself可以視作代碼管理的一種手段2. functional language沒有類的概念,只有函數和模塊3. JVM上的scala, clojure,BEAM上的elixir4. 我還沒講這種結構下concurrency,deployment的優勢呢5. 也不盡然,愛立信用erlang寫的交換機軟體達到了9sigma6. 一次處理一個消息,一對actor間的消息順序是保證的7. 確切地說是用erlang寫出的交換機軟體達到了9 sigmas推薦閱讀:
※英特爾酷睿i5處理器能安裝win7 64位操作系統嗎?
※為什麼現在主板很少有支持win7的?
※電腦win7系統C盤多大最為合適?
※現在買蘋果6,蘋果6s,蘋果7,還是蘋果8更划算?
※iOS 7 beta 現在有哪些嚴重影響日常使用的 Bug ?