眾里尋他千百度粪薛,驀然回首悴了,那人卻在,燈火闌珊處违寿。
一般地湃交,在一個程序員的日常工作之中,絕大多數(shù)時間都是在「閱讀代碼」陨界,而不是在「寫代碼」巡揍。但是菌瘪,閱讀代碼往往是一件很枯燥的事情,尤其當遇到了一個不漂亮的設(shè)計糜工,反抗的心理往往更加強烈捌木。
事實上,變換一下習(xí)慣澈圈、思路和方法瞬女,代碼閱讀其實是一個很享受的過程努潘。閱讀代碼的模式,實踐和習(xí)慣疯坤,集大成者莫過于希臘作者Diomidis Spinellis
的經(jīng)典之作:Code Reading, The Open Source Perspective.
压怠。本文從另外一個視角出發(fā),談?wù)勎易约洪喿x代碼的一些習(xí)慣洋闽,期待找到更多知音的共鳴突梦。
工欲善其事宫患,必先利其器
首先,閱讀代碼之前先準備好一個稱心如意的工具箱虚汛,包括IDE
, UML
卷哩,Mind Maping
等工具属拾。我主要使用的編程語言包括C++, Scala, Java, Ruby
;對于Scala, Java, Ruby
編程尊浓,我更偏向使用JetBrain
公司的產(chǎn)品栋齿;而對于C++
編程,我依然還在使用Eclipse
基协,因為Clion
的特性還沒有讓我滿意堡掏。
其次,高效地使用快捷鍵鹅龄,這是一個良好的代碼閱讀習(xí)慣扮休,它極大地提高了代碼閱讀的效率和質(zhì)量。例如蜗搔,查看類層次關(guān)系八堡,函數(shù)調(diào)用鏈兄渺,方法引用點等等挂谍。
拔掉鼠標,減低對鼠標的依賴炼绘。當發(fā)現(xiàn)沒有鼠標而導(dǎo)致工作無法進行下去時俺亮,嘗試尋找對應(yīng)的快捷鍵驮捍。通過日常的點滴積累厌漂,工作效率必然能夠得到成倍的提高苇倡。
力行而后知之真
閱讀代碼一種常見的反模式就是「通過Debug
的方式來閱讀代碼」旨椒。作者不推薦這種代碼閱讀的方式堵漱,其一勤庐,因為運行時線程間的切換很容易導(dǎo)致方向的迷失愉镰;其二,了解代碼調(diào)用棧對于理解系統(tǒng)行為并非見得有效录择,因為其包含太多實現(xiàn)細節(jié)隘竭,不易發(fā)現(xiàn)問題的本質(zhì)讼渊。
但在閱讀代碼之前,有幾件事情是必須做的弧圆。其一搔预,手動地構(gòu)建一次工程拯田,并運行測試用例甩十;其二,親自動手寫幾個Demo
感受一下鸭轮。
先將工程跑起來窃爷,目的不是為了Debug
代碼按厘,而是在于了解工程構(gòu)建的方式,及其認識系統(tǒng)的基本結(jié)構(gòu)卿堂,并體會系統(tǒng)的使用方式草描。
如果條件允許策严,可以嘗試使用ATDD
的方式享钞,發(fā)現(xiàn)和挖掘系統(tǒng)的行為栗竖。通過這個過程,將自己當成一個客戶狐肢,思考系統(tǒng)的行為沥曹,這是理解系統(tǒng)最重要的基石妓美。
發(fā)現(xiàn)領(lǐng)域模型
發(fā)現(xiàn)「領(lǐng)域模型」是閱讀代碼最重要的一個目標壶栋,因為領(lǐng)域模型是系統(tǒng)的靈魂所在。通過代碼閱讀琉兜,找到系統(tǒng)本質(zhì)的模型豌蟋,并通過自己的模式表達出來梧疲,你才能真正地Hold
住了系統(tǒng),否則一切都是空談擂找。
首要的任務(wù)贯涎,就是找到系統(tǒng)的邊界塘雳,并能夠以「抽象的思維」思考外部系統(tǒng)的行為特征败明。其次妻顶,尋找系統(tǒng)潛在的讳嘱,并能表達系統(tǒng)的重要概念酿愧,及其它們之間的關(guān)聯(lián)關(guān)系嬉挡。
細節(jié)是魔鬼
糾結(jié)于細節(jié)庞钢,將導(dǎo)致代碼閱讀代碼的效率和質(zhì)量大大折扣焊夸。例如仁连,日志打印饭冬,解決Bug
的補丁實現(xiàn)昌抠,某版本分支的兼容方案,某變態(tài)用戶需求的錘子代碼等等裁厅。
閱讀代碼的一個常見的反模式就是「給代碼做批注」执虹。這是一個高耗低效唠梨,投入產(chǎn)出比極低的實踐袋励。越是優(yōu)雅的系統(tǒng),注釋越少当叭;越是復(fù)雜的系統(tǒng)茬故,再多的注釋也是于事無補。
我有一個代碼閱讀的習(xí)慣蚁鳖,為代碼閱讀建立一個單獨的code-reading
分支磺芭,一邊閱讀代碼,一邊刪除這些無關(guān)的代碼醉箕。
$ git checkout -b code-reading
刪除這些噪聲后钾腺,你會發(fā)現(xiàn)系統(tǒng)根本沒有想象之中那么復(fù)雜。事實上讥裤,系統(tǒng)的復(fù)雜性,往往都是之前不成熟的設(shè)計和實現(xiàn)導(dǎo)致的額外復(fù)雜度剧辐。
適可而止
閱讀代碼的一個常見的反模式就是「一根筋走到底,不到黃河絕不死心」。程序員都擁有一顆好奇心鳄梅,總是對不清楚的事情感興趣冤狡。例如,消息是怎么發(fā)送出去的坦胶?任務(wù)調(diào)度工作原理是什么岖圈?數(shù)據(jù)存儲怎么做到的等等导匣;雖然這種勇氣值得贊揚,但在代碼閱讀時絕對不值得鼓勵渠牲。
還有另外一個常見的反模式就是「追蹤函數(shù)調(diào)用棧」谚咬。這是一個極度枯燥的過程背苦,常常導(dǎo)致思維的僵化;因為你永遠活在作者的陰影下,完全沒有自我。
我個人閱讀代碼的時候,函數(shù)調(diào)用棧深度絕不超過3
,然后使用抽象的思維方式思考底層的調(diào)用。因為我發(fā)現(xiàn),隨著年齡的增長,曾今值得驕傲的記憶力誉己,現(xiàn)在逐漸地變成自己的短板。當我嘗試追蹤過深的調(diào)用棧之后坪蚁,之前的閱讀信息完全地消失記憶了。
也就是說男摧,我更習(xí)慣于「廣度遍歷」奏司,而不習(xí)慣于「深度遍歷」的閱讀方式。這樣,我才能找到系統(tǒng)隱晦存在的「分層概念」齿桃,并理順系統(tǒng)的結(jié)構(gòu)报破。
發(fā)現(xiàn)她的美
三人行,必有我?guī)熝伞T诖a閱讀代碼時柳琢,當發(fā)現(xiàn)好的設(shè)計,包括實現(xiàn)模式秦爆,習(xí)慣用法等望门,千萬不要錯過厨剪;否則過上一段時間丽惶,這次代碼閱讀對你來說就沒有什么價值了抡秆。
當我發(fā)現(xiàn)一個好的設(shè)計時着撩,我會嘗試使用類圖,狀態(tài)機奋救,時序圖等方式來表達設(shè)計演侯;如果發(fā)現(xiàn)潛在的不足,將自己的想法補充進去背亥,將更加完美秒际。
例如悬赏,當我閱讀Hamcrest
時,嘗試畫畫類圖娄徊,并體會它們之間關(guān)系闽颇,感受一下設(shè)計的美感,也是受益頗多的寄锐。
嘗試重構(gòu)
因為這是一次代碼閱讀的過程兵多,不會因為重構(gòu)帶來潛在風(fēng)險的問題。在一些復(fù)雜的邏輯橄仆,通過重構(gòu)的等價變換可以將其變得更加明晰剩膘,直觀。
對于一個巨函數(shù)盆顾,我常常會提取出一個抽象的代碼層次怠褐,以便發(fā)現(xiàn)它潛在的本質(zhì)邏輯。例如您宪,這是一個ArrayBuffer
的實現(xiàn)奈懒,當需要在尾部添加一個元素時,既有的設(shè)計是這樣子的蚕涤。
def +=(elem: A): this.type = {
if (size + 1 > array.length) {
var newSize: Long = array.length
while (n > newSize)
newSize *= 2
newSize = math.min(newSize, Int.MaxValue).toInt
val newArray = new Array[AnyRef](newSize)
System.arraycopy(array, 0, newArray, 0, size)
array = newArray
}
array(size) = elem.asInstanceOf[AnyRef]
size += 1
this
}
這段代碼給閱讀造成了極大的障礙筐赔,我會通過快速的函數(shù)提取,發(fā)現(xiàn)邏輯的主干揖铜。
def +=(elem: A): this.type = {
if (atCapacity)
grow()
addElement(elem)
}
至于atCapacity, grow, addElement
是怎么實現(xiàn)的茴丰,壓根不用關(guān)心,因為我已經(jīng)達到閱讀代碼的效果了天吓。
形式化
當閱讀代碼時贿肩,有部分人習(xí)慣畫程序的「流程圖」。相反龄寞,我?guī)缀鯊膩聿粫嫛噶鞒虉D」汰规,因為流程圖反映了太多的實現(xiàn)細節(jié),而不能深刻地反映算法的本質(zhì)物邑。
我更傾向于使用「形式化」的方式來描述問題溜哮。它擁有數(shù)學(xué)的美感,簡潔的表達方式色解,及其高度抽象的思維茂嗓,對挖掘問題本質(zhì)極其關(guān)鍵。
例如科阎,對于FizzBuzzWhizz
的問題述吸,相對于冗長的文字描述,流程圖等方式锣笨,形式化的方式將更加簡單蝌矛,并富有表達力道批。
以3, 5, 7
為輸入,形式化后描述后入撒,可清晰地挖掘出問題的本質(zhì)所在隆豹。
r1: times(3) => Fizz ||
times(5) => Buzz ||
times(7) => Whizz
r2: times(3) && times(5) && times(7) => FizzBuzzWhizz ||
times(3) && times(5) => FizzBuzz ||
times(3) && times(7) => FizzWhizz ||
times(5) && times(7) => BuzzWhizz
r3: contains(3) => Fizz
rd: others => string of others
spec: r3 || r2 || r1 || rd
實例化
實例化是認識問題的一種重要方法,當邏輯非常復(fù)雜時茅逮,一個簡單例子往往使自己豁然開朗噪伊。在理想的情況下,實例化可以做成自動化的測試用例氮唯,并以此描述系統(tǒng)的行為。
如果存在某個算法和實現(xiàn)都相當復(fù)雜時姨伟,也可以通過實例化探究算法的工作原理惩琉,這對于理解問題本身大有益處。
以Spark
中劃分DAG
算法為例夺荒。假設(shè)G
為FinalRDD
瞒渠,從后往前按照RDD
的依賴關(guān)系,依次識別出各個Stage
的起始邊界技扼。
-
Stage 3
的劃分:-
G
與B
之間是Narrow Dependency
伍玖,規(guī)約為同一Stage(3)
; -
B
與A
之間是Wide Dependency
,A
為新的FinalRDD
剿吻,遞歸調(diào)用此過程窍箍; -
G
與F
之間是Wide Dependency
,F
為新的FinalRDD
丽旅,遞歸調(diào)用此過程椰棘;
-
-
Stage 1
的劃分-
A
沒有父親RDD
,Stage(1)
劃分結(jié)束榄笙。特殊地Stage(1)
僅包含RDD A
邪狞;
-
-
Stage 2
的劃分:- 因
RDD
之間的關(guān)系都為Narrow Dependency
,規(guī)約為同一個Stage(2)
; - 直至
RDD C, E
茅撞,因沒有父親RDD
帆卓,Stage(2)
劃分結(jié)束;
- 因
最終米丘,形成了Stage
的依賴關(guān)系剑令,依次提交Stage(TaskSet)
至TaskScheduler
進行調(diào)度執(zhí)行。
獨樂樂不如眾樂樂
與他人分享你的經(jīng)驗蠕蚜,也許可以找到更多的啟發(fā)尚洽;尤其對于熟知該領(lǐng)域的人溝通,如果是Owner
就更好了靶累,更能得到意外的驚喜和收獲腺毫。
也可以通過各種渠道癣疟,收集他人的經(jīng)驗,并結(jié)合自己的思考潮酒,推敲出自己的理解睛挚,如此才能將知識放入自己的囊中。
- 文/潘曉璐 我一進店門绳军,熙熙樓的掌柜王于貴愁眉苦臉地迎上來印机,“玉大人,你說我怎么就攤上這事门驾∩淙” “怎么了?”我有些...
- 文/不壞的土叔 我叫張陵奶是,是天一觀的道長楣责。 經(jīng)常有香客問我,道長聂沙,這世上最難降的妖魔是什么腐魂? 我笑而不...
- 正文 為了忘掉前任,我火速辦了婚禮逐纬,結(jié)果婚禮上蛔屹,老公的妹妹穿的比我還像新娘。我一直安慰自己豁生,他們只是感情好兔毒,可當我...
- 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著甸箱,像睡著了一般育叁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芍殖,一...
- 文/蒼蘭香墨 我猛地睜開眼钦睡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了躁倒?” 一聲冷哼從身側(cè)響起荞怒,我...
- 正文 年R本政府宣布骄崩,位于F島的核電站,受9級特大地震影響薄辅,放射性物質(zhì)發(fā)生泄漏要拂。R本人自食惡果不足惜,卻給世界環(huán)境...
- 文/蒙蒙 一站楚、第九天 我趴在偏房一處隱蔽的房頂上張望脱惰。 院中可真熱鬧,春花似錦牌芋、人聲如沸战授。這莊子的主人今日做“春日...
- 文/蒼蘭香墨 我抬頭看了看天上的太陽蔚润。三九已至,卻和暖如春尺栖,著一層夾襖步出監(jiān)牢的瞬間嫡纠,已是汗流浹背。 一陣腳步聲響...
推薦閱讀更多精彩內(nèi)容
- 該論文來自Berkeley實驗室蠢棱,英文標題為:Resilient Distributed Datasets: A ...
- Spark RDD(Resilient Distributed Datasets)論文 概要 1: 介紹 2: R...
- Spark RDD(Resilient Distributed Datasets)論文 概要 1: 介紹 2: R...
- 對于新時代的女性來說應(yīng)酬總是少不了的泻仙,這即是女性該有的涉交能力糕再,也是必備的職場能力。談業(yè)務(wù)玉转、陪客戶突想、接待客人、與領(lǐng)...