代碼不是文學作品
我在最近兩個工作過的公司—Etsy 和 推ter—成立過代碼閱讀小組,一些人向我征詢關于閱讀代碼以及運作代碼閱讀小組的建議。想說的太多,一言以蔽之:不要成立代碼閱讀小組。你應該去 成立小組但不是我稍后提到的那樣,而在此之前,我要解釋一下我是如何得出目前觀點的。
作為一位曾讀英語專業的業余作者,我以往總是被這樣的想法所吸引:代碼就像文學作品,我們應該像通過閱讀優秀范文學寫英文那樣來學寫代碼。當然,持這種觀點的不止我一個人。Donald Knuth(著名計算機科學家,譯者注),除了致力開發 TeX 軟件和編著《計算機程序設計藝術》,一直以來是被他稱作“文學編程”的提倡者,并已經把他的幾個大型程序出版成書。
另一方面,早在我加入 Etsy 并成立第一個代碼閱讀小組之前,手頭上就已經有一些跡象本就表明:這是看待代碼的一個錯誤方式。
首先,當我在編寫關于與程序員訪談的《編程人生》 (書中記錄與 15 位世界級編程大師的對話,譯者注)一書時,我幾乎問了每個人關于閱讀代碼的問題。大部分人說閱讀代碼是重要的且程序員應該多讀,然而當我問到他們最近讀了 哪些代碼時,很少有人有好的答案。在他們還是年輕黑客時,其中有些人認真地讀過一些代碼,然而幾乎沒人保有閱讀代碼的習慣。Knuth 作為偉大的計算機科學集大成者,閱讀了大量的代碼;Brad Fitzpatrick 談到出于惡作劇而閱讀過一些開源代碼片段。但他們倆是例外。
如果這還不夠,那么在完成《編程人生》之后,我獲得一次采訪 Hal Abelson 的機會。Hal Abelson 是麻省理工大學的著名教授,《計算機程序的構造和解釋》 的合著者。當我首次跟他談論,問及之前那樣的閱讀代碼問題時,他給出了普遍的答案:閱讀代碼是重要的且程序員應該多讀。但他也沒有說出任何最近讀過的代 碼,除了不得不閱讀的代碼之外,包括在谷歌休公假時審查同事的代碼以及給麻省理工學生評分時的代碼。后來我問他關于這種矛盾:
Seibel: 我對人們所說的和實際所做的之間的這種分裂很好奇。每個人說“程序員應該閱讀代碼”,但似乎少數人真正去做了。我會感到驚訝: 如果我采訪小說家們并問他們最近讀了哪本小說,他們回答說“哦,自從讀研究生后,我就沒真正地去讀一本小說了”。作家們實際上會讀其他作者的書,但程序員 不會真正得去讀其他人的代碼,盡管他們說應該去讀。
Abelson: 是的。你是對的。但記住,很多時候,你增刪查改一個程序最終使之能夠運行并完成你需要它做的所有事情,所以就有很多與核心思想無關緊要的東西。
Seibel: 所以基本上你最終是說,大多數代碼不值得一讀?
Abelson: 或者說程序從一個初始計劃或某種偽代碼構建起來。書中的許多代碼,是一些凈化過的版本,不具備使程序運行起來的所有東西。
Seibel: 我想起了《計算機程序的構造和解釋》的前言,里面寫道“程序應該是為了供人讀才寫的,然后順便讓機器執行了一下。”然而正好你所描述的事實在實際中卻是,大多數程序都是讓機器來執行才寫的,如果有的話,只順便供人閱讀。
Abelson: 嗯,我認為他們一開始是為了供人閱讀的,因為里面包含著一些想法。你解釋的東西,僅有一小部分存在我們的書中。書中有一些相當重要的程序。而且部分原因是我們認為解釋它做什么的最簡單方式就是表達在代碼中。
然而即使明顯知道大部分真正的代碼實際上不是采用人容易讀懂的方式去編寫的,但這不足以讓我在 Etsy 成立小組時放棄這種文學研討班模式。由于大多數 Etsy 開發者熟悉 Javascript 以及我知道 Jeremy Ashkenas 對可讀性代碼的編寫有濃厚興趣,所以在第一次代碼閱讀小組會時,我選取了 Jeremy 的 backbone.js。我仍計劃了一些像文學研討會一樣的流程,但我發現許多人不會預習代碼(嗯,這點跟文學研討班倒是一樣)。所以我決定在小組討論環 節之前演示要討論的代碼。
當準備我的演示文檔時,我發現自己掉進了慣常模式:每當試圖真正理解或心領神會一段代碼時,我不得不從根本上重寫它。為了更好理解,我將開始重 命名一些名稱,接著順著我組織代碼思路去移動一些語句。很快,我就已經開始深入代碼抽象化(或具體化),并開始對更大程度地重組代碼結構。一旦完成代碼重 寫,我通常已很好地領悟了,甚至可以溯源并理解初始版本。我經常覺得這種閱讀代碼的方式不好,但它是迄今為止我理解代碼的唯一方式。
在代碼閱讀小組演示時,我以初始的 backbone.js 作為開始,然后在自己想法的指引下,一步一步演變使之更易懂。當我問大家是否應該轉向小組討論環節時,但似乎沒有人很感興趣。好在看到我的重構讓組員對原始代碼的底層結構有了與之前我通過重構獲得的相同見解。
Etsy 的第二次代碼閱讀小組會由 Avi Bryant 主持,展示了如何利用 SmallTalk 的代碼瀏覽功能來查看一些代碼。因為在 Etsy 少數工程師跟 Smalltalk 打過交道,所以我們對組員會預習代碼這件事不抱任何希望。但這次演示,對組員來說是一次領略 SmallTalk 魅力的絕佳機會,也讓有我有機會發難 Avi 關于 Smalltalk 和 Lisp 的區別。
當我來到 推ter,文學研討會模式仍莫名其妙地盤旋在腦海中,盡管在 Etsy 的兩次看起來備受青睞的小組會幾乎沒有遵循這種模式。當我發郵件邀請 推ter 的工程師參加代碼閱讀小組時,回應相當熱情。再次,第一次小組會由 Marius Eriksen 演示了一段代碼。在這個示例中,展示了 Scala 語言實現的 Future 包的內部構件。這個構件被用于 推ter 的很多服務,其大部分由 Eriksen 本人編寫。
在演示了一段時間后,我終于明白一個淺顯的道理:代碼不是文學作品。我們不去閱讀代碼,而是解譯、審查它。一段代碼不是文學片段,而是樣本。當我問 Knuth 關于他自己閱讀代碼時,他回答的其實早就給我指明了這個方向:
Knuth: 但建立在大腦的東西是真正有價值的。那么我怎么利用它的呢?曾經有臺型號為 Bunker Ramo 300 的機器,有人告訴我這臺機器的 Fortran 編譯器真的是不可思議的快,但沒有人知道它是怎么做到的。我得到了編譯器源代碼的副本。因為我沒有機器的說明書,所以甚至連機器語言是什么都不知道。
但是我把它當成是一項有趣的挑戰。我能搞清楚 BEGIN,然后我就開始解碼。這些指令碼對應著雙字指令助記符,所以我開始合計著“這個可能是裝載指令,而這個可能是分支”。接著我知道它是 Fortran 編譯器,所以到一定程度后,看著卡片的第七列就能分辨是不是注釋。
三個小時后,我已經弄懂機器的一小部分。接著我碰到了重大問題 — 跳轉表。因此這是一個難題,之后我堅持畫了些圖表,就像我在某安全局試圖解譯一個密碼一樣。但我知道它是個運行很快的 Fortran 編譯器,在某種意義來說這不是加密而是有意地掩蓋;因為弄不到機器的說明書,所以答案就藏在代碼中。
最終我弄明白了為什么這個編譯器如此快。不幸的是,它并不是因為使用了卓越的算法,僅僅是因為他們采用非結構化編程并且最大限度地優化了代碼。
畫圖表、獲取更多一點信息并作假設僅是你基本上解決某種難題的方法。通常,當我閱讀技術文件時,是同樣的挑戰。我試圖理解作者的思想,嘗試搞懂這個概念是什么。我認為,你試著去讀別人的東西越多,未來你更能創造屬于自己的東西。
他沒有在描述文學作品的閱讀,而在描述一個科學性調查。所以現在我對人們應該如何一起從代碼中獲得深刻見解這個問題有了新答案,正如我向 推ter 代碼閱讀小組解釋的一樣:
在準備與一起編程的女兒們即將進行的對話時,我開始思考告訴她們一些關于代碼閱讀以及應該讀什么代碼的事情。我再次突然 想到了那些所有口惠而實不至的代碼閱讀想法,絕大部分程序員不會真正地閱讀大量代碼,至少不是純粹為了閱讀代碼而讀。這有一個簡單檢驗方法:說出一段你讀 過的代碼并且肯定大部分優秀程序員會去讀或者至少聽說過。不多,對不對?可能一行都沒有。
但我想到代碼不是文學作品,我們不是讀者。更確切地說,有趣的代碼片段是樣本,我們是博物學家。所以我認為更好的方式 是:我們其中的一位扮演著一個剛從異國回來的 19 世紀博物學家在國內科學界引發關于他們所發現的一只新型昆蟲的議論:“看這怪物的觸須!這些觸須看起來非常笨拙,然而該物種的雄性可利用它們殺死小青蛙, 然后雌性將卵產在其尸體中”,而不是像一群學比較文學的研究生那樣挑出一段代碼并去閱讀、討論它。
這種演示的關鍵在于介紹者要選一段自己深刻理解的代碼,并通過從進化碎屑層(亦為臨時補救方案–指快速有效但丑陋的方 案,譯者注)中指出核心思想,有助于聽眾理解。一種合理的方式應該是展示真正的源碼并剝離、重寫關鍵部分,就像一個生物學家去染色樣本,使不同特征更容易 辨別一樣。
典型的演示應面向所有程序員,或男或女,或聰明或一般,但必須對與代碼出處有關的任何特定知識不作要求。為了讓組員理解代碼,演示文檔應該提供足夠的上下文,解釋對一般水平程序員而言比較晦澀的實現語言的任何細節。
由于我的頓悟,我們采用新模式,已成功舉行了幾次代碼閱讀小組會議,現在被稱為 推ter 的旨在提高編碼知識的英國皇家學會。我們仍在摸索講演代碼的最佳方法,盡管目前方法感覺很不錯。另外,我不再覺得我這種解剖式閱讀代碼的方式是糟糕的。
至今最大的教訓是,代碼是非常稠密的。半小時的報告只夠用來呈現大約十二行耐人尋味的代碼和一個主要思想。幾乎可以肯定的是:報告人必須真正深 入某段代碼,要比任何人有更深的理解。除此之外,一個優秀的演示至少可讓組員接觸到的核心思想,對于決定自己去閱讀代碼的人們來說,可能是一個好的開端。
譯文鏈接: http://blog.jobbole.com/64548/