為什麽我們要閱讀源碼?
https://zhuanlan.zhihu.com/p/26181360
*****************************
程序員每天都和代碼打交道。經過數年的基礎教育和職業培訓,大部分程序員都會「寫」代碼,或者至少會抄代碼和改代碼。但是,會讀代碼的並不在多數,會讀代碼又真正讀懂一些大項目的源碼的,少之又少。這種怪狀,真要追究起來,怪不得程序員這個群體本身 —— 它是兩個原因造成的:
-
我們所有的教育和培訓都在強調怎麽寫代碼,並沒有教大家如何讀代碼
-
大多數工作場景都是一個蘿蔔一個坑,我們只需要了解一個系統的局部便能開展工作,讀不相幹的代碼,似乎沒用
我常常把寫代碼和寫作進行類比 —— 二者有很多相通之處;但從培養寫代碼和寫作的過程來看,二者又有很多不同。我們的寫作能力,是建立在大量基礎閱讀的基礎上的,是除了學習語法和文法知識外,從小學開始,經年累月,通過閱讀各種不同層次的名家的作品,再加上各種各樣的寫作訓練,累積出來的;而我們的寫代碼的能力,在了解和掌握了語法/文法之後(學習和抄寫 example 代碼也算語法/文法學習的一部分),跳過了大量閱讀名家作品的過程,直接 biu 地一下就自動養成了:學會基礎的語法和試驗了若幹 example 後,我們就火箭般躥到了自己寫代碼打怪贊經驗的階段。這樣略過大量閱讀代碼的階段有三個害處:
-
寫代碼的基礎是不牢靠的,打怪升級的過程也是最慢的。道理很簡單 —— 前輩們踩過的坑,總結的經驗教訓,你都不得不親自用最慢的法子一點點試著踩一遍。
-
很容易養成 stackoverflow driven 的寫代碼習慣 —— 遇到不知如何寫的代碼,從網上找現成的答案,找個高票的復制粘貼改吧改吧,湊活著完成功能再說。寫代碼的過程中遇到問題,開啟調試模式,要麽設置無數斷點一步步跟蹤,要麽到處打印信息試圖為滿是窟窿的代碼打上補丁,導致整個寫代碼的過程是一部調代碼的血淚史。(見我的文章:你要避免的軟件開發模式)
-
你周圍最強的那個工程師的開發水平的上限就是你的上限。
我們再回到讀書進行類比。
從小學到高中,就語文而言,12年時光,單單課本我們要讀十二冊,數百篇文章。如果每篇文章平均一千字,那麽我們讀了數十萬文字。這些文字,我們是精讀過的(有些甚至要求全文背誦)。每篇文章我們需要總結中心思想,段落大意,歸納出論點論證論據或者時間任務地點起因經過結果,會分析長句難句,會學習起承轉結,並反復訓練基礎的遣詞造句能力,並最終模仿那些文章寫出自己的文章。在這個過程中,我們學會了賦、比、興,我們掌握了三段論,我們知道了如何用更優雅地方式表達自己的思想。更重要的是,這些閱讀訓練讓我們在我們在脫離學校的基礎教育後,可以自己獨立完成一本書的閱讀。我們不再依賴老師或者參考書為我們給出段落大意,中心思想,我們知道如何粗讀,細讀甚至類比閱讀一本書,我們能把書中的精髓濃縮成思維導圖,也大段大段摘錄書中精彩的句子,段落或者篇章。
這十幾年的時光,算上各種課外閱讀,世界名著,古典文學,金庸古龍,修正玄幻,一個大學畢業的二十幾歲的青年人,閱讀量應該不下幾百萬字。而稍稍涉獵廣些的讀者,上千萬字的閱讀累積是常有的事。有了這些累積,你才能在迎面走來一位妙齡女子,想到的是肌膚勝雪,明眸善睞,桃腮帶笑,齒如含貝,氣若幽蘭,美艷不可方物,一笑傾城,再笑傾國,沈魚落雁,閉月羞花這些詞句,而非不知如何表達,只能吞吞口水,在肚子裏悶上一句:「我擦,美女啊」。
這是讀書的第一大功用:累積素材(information)。你是否寫文章時,經過一番搜腸刮肚,也不知該如何描述某事某物?同樣的,寫代碼時,有沒有毫無頭緒,不知從何寫起的時候?或者有了些的思路,雙手卻在鍵盤上遲滯,不知所措?這些現象,大多是缺乏累積所致。
讀書的第二大功用:是開拓思路。有時候,一段文字,甚至一個句子,在你意料之外撲面而來,讓你有種醍醐灌頂的感覺。比如『圍城』裏,趙辛楣和方鴻漸鴻初次見面,錢老描述趙的傲慢無禮,是這麽寫的:「傲兀地把他從頭到腳看一下,好像鴻漸是本一覽而盡的大字幼稚園讀本」。初讀圍城的時候,我關註點是其故事性,將這樣的句子輕易放了過去,幾年前再讀時,才發覺它的精妙:竟能如此簡單地以物喻人,就把整個場景復原到如同發生在我的面前一樣活靈活現。隨後,我自己的文字裏也模仿著,有時甚至刻意地如此這般使用比喻來增強畫面感。前些日子偶爾再讀到這句,因我有了作為一個成人,給女兒讀幼稚園讀本的經驗,不由得莞爾一笑,旋即明白了一個道路:精妙的不是比喻本身,而是對生活的細微觀察。
我在邊學 elixir 邊做 policy engine(見:Policy Engine 的前世今生)的過程中,除了官方的文檔和零星的博文外,可讀的內容少得可憐,我要解決的一些問題,論壇裏也沒人能給我較好的思路。於是我轉而讀了部分 iex 的代碼,了解了 elixir 代碼編譯的方式,最終完成了一個 auto compiler 的 app —— 它能接受一些 API 請求,對預先配置的屬於其他 app 下的源碼可以在 cluster 裏的有且僅有一個 node 上進行編譯,編譯完成後在整個 cluster 的所有 node 裏重新加載;在做 API 的過程中,我讀了 plug(elixir 下官方的 connection adapter 實現)裏面的主要邏輯,尤其是精讀了 Plug.Router 的代碼,搞明白了為何 Phoenix 的 router 敢宣稱在 route match 階段,其 performance 就甩同行好幾個數量級。於是我做 API 時,對如何在 match / dispatch 前後如何做些動作實現 middleware,甚至 hook 進 before send,有了更清晰的思路,在寫代碼時,也更加明白如何寫出類似的 composable adapter。
累積素材是基礎,被啟發出來的思路將這些素材串成線,這就形成了知識(knowledge)。書讀得越多,越勤於思考的人,知識也就越豐富。而知識的融會貫通,最終形成讀書的第三大功用:通過了解,吸收別人的思想,去蕪存菁,最終形成自己的思想,或者說智慧(wisdom)。
information -> knowledge -> wisdom 是個長期的累積,並非一朝一夕之功。
我的文章除幾篇廣告外,全部是原創,而原創中 99% 是我自己原創。這些原創完完全全是我「原創」麽?我在 『如何選擇工作』一文中借用了『黑客與畫家』的思想:可測量性和可放大性。我贊同這個思想,同時將其融入了我自己的思考;在『程序員和拉條子』一文,我其實是寫了個現代版的庖丁解牛;『代碼重構之道』我借用了松本行弘,Martin Fowler,荀子等人的思想,形成了我自己的見解。當然,我書還是讀得太少,所以,真正有價值的思想還貧瘠得很。
同樣的,閱讀名家的優秀的代碼最終的歸宿是形成你自己寫代碼的思路。我去年撰寫的一個基於 node restify 的 API framework(見:再談 API 的撰寫 - 總覽 系列),雖然有很多不完善的地方,但內核還是相當穩定,一年多來只是些許小修小補。撰寫它的過程,是我對之前所讀各種項目的一個融會貫通:為了確定我是否該基於 restify,我通讀了它的代碼(沒多少),融合進了 hapi 和 loopback 的一些思路,提供了一套類似於 rails 的 CLI,再加上早年在 parser 上的一點經驗,把 framework 邏輯上分出了:compile time(嚴格講 nodejs 沒有 compile time,但 configuration 的處理,route / middleware / model / task 對應的內部數據結構的生成,算進了 compile time 中),load time 和 run time,用前兩個階段的「低效」換取後一個階段的高效(其實也類比了我的老本行,路由器/防火墻上的 control plane / data plane,first path / fast path 的設計)。如果沒有之前各種源碼閱讀的累積,我很難做出這樣的設計。
同樣的,閱讀 elixir unicode 實現的過程在兩年前激發我做了一個使用同樣思路的漢字的 slugify,或者漢字轉拼音(解決多音字問題)的暴力方法(see github: chinese_translation),這一思路在今年年初進一步發揚光大成了我們現在在 Tubi TV 使用的 policy engine 和 content engine,為每日不計其數的內容請求保駕護航。我們使用了 code as cache 的方法,把數據庫裏的內容的不必在運行時做的 transformation 都在 compile time 完成,然後在 run time 結合記敘文三要素,得到最終的結果。以前我們系統中我們最慢的 API,采用新的 engine,如今快了至少數十倍 —— 尤其是在低並發下就慘不忍睹的 95 percentile response time(高並發都無法完成測試),如今在高並發,沒有任何 failure 的情況下,99 percentile response time 都能控制在 500ms 左右。
所以你說,閱讀優秀的,有啟發性的源代碼,多重要?
解決了 why 的問題,過兩天我們講講 how。
(PS:最近天氣轉熱,從冬天直接跳到夏天,所以容易便秘 —— 一不小心一篇文章憋出了兩篇 ^_^)
為什麽我們要閱讀源碼?