北京尚学堂|程序员的智慧
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
版權(quán)聲明:本文為北京尚學(xué)堂原創(chuàng)文章,未經(jīng)允許不得轉(zhuǎn)載。
?
編程是一種創(chuàng)造性的工作,是一門(mén)藝術(shù)。精通任何一門(mén)藝術(shù),都需要很多的練習(xí)和領(lǐng)悟,所以這里提出的“智慧”,并不是號(hào)稱(chēng)一天瘦十斤的減肥藥,它并不能代替你自己的勤奮。然而由于軟件行業(yè)喜歡標(biāo)新立異,喜歡把簡(jiǎn)單的事情搞復(fù)雜,我希望這些文字能給迷惑中的人們指出一些正確的方向,讓他們少走一些彎路,基本做到一分耕耘一分收獲。
反復(fù)推敲代碼
既然“天才是百分之一的靈感,百分之九十九的汗水”,那我先來(lái)談?wù)勥@汗水的部分吧。有人問(wèn)我,提高編程水平最有效的辦法是什么?我想了很久,終于發(fā)現(xiàn)最有效的辦法,其實(shí)是反反復(fù)復(fù)地修改和推敲代碼。
在IU的時(shí)候,由于Dan Friedman的嚴(yán)格教導(dǎo),我們以寫(xiě)出冗長(zhǎng)復(fù)雜的代碼為恥。如果你代碼多寫(xiě)了幾行,這老頑童就會(huì)大笑,說(shuō):“當(dāng)年我解決這個(gè)問(wèn)題,只寫(xiě)了5行代碼,你回去再想想吧……” 當(dāng)然,有時(shí)候他只是夸張一下,故意刺激你的,其實(shí)沒(méi)有人能只用5行代碼完成。然而這種提煉代碼,減少冗余的習(xí)慣,卻由此深入了我的骨髓。
有些人喜歡炫耀自己寫(xiě)了多少多少萬(wàn)行的代碼,仿佛代碼的數(shù)量是衡量編程水平的標(biāo)準(zhǔn)。然而,如果你總是匆匆寫(xiě)出代碼,卻從來(lái)不回頭去推敲,修改和提煉,其實(shí)是不可能提高編程水平的。你會(huì)制造出越來(lái)越多平庸甚至糟糕的代碼。在這種意義上,很多人所謂的“工作經(jīng)驗(yàn)”,跟他代碼的質(zhì)量,其實(shí)不一定成正比。如果有幾十年的工作經(jīng)驗(yàn),卻從來(lái)不回頭去提煉和反思自己的代碼,那么他也許還不如一個(gè)只有一兩年經(jīng)驗(yàn),卻喜歡反復(fù)推敲,仔細(xì)領(lǐng)悟的人。
有位文豪說(shuō)得好:“看一個(gè)作家的水平,不是看他發(fā)表了多少文字,而要看他的廢紙簍里扔掉了多少。” 我覺(jué)得同樣的理論適用于編程。好的程序員,他們刪掉的代碼,比留下來(lái)的還要多很多。如果你看見(jiàn)一個(gè)人寫(xiě)了很多代碼,卻沒(méi)有刪掉多少,那他的代碼一定有很多垃圾。
就像文學(xué)作品一樣,代碼是不可能一蹴而就的。靈感似乎總是零零星星,陸陸續(xù)續(xù)到來(lái)的。任何人都不可能一筆呵成,就算再厲害的程序員,也需要經(jīng)過(guò)一段時(shí)間,才能發(fā)現(xiàn)最簡(jiǎn)單優(yōu)雅的寫(xiě)法。有時(shí)候你反復(fù)提煉一段代碼,覺(jué)得到了頂峰,沒(méi)法再改進(jìn)了,可是過(guò)了幾個(gè)月再回頭來(lái)看,又發(fā)現(xiàn)好多可以改進(jìn)和簡(jiǎn)化的地方。這跟寫(xiě)文章一模一樣,回頭看幾個(gè)月或者幾年前寫(xiě)的東西,你總能發(fā)現(xiàn)一些改進(jìn)。
所以如果反復(fù)提煉代碼已經(jīng)不再有進(jìn)展,那么你可以暫時(shí)把它放下。過(guò)幾個(gè)星期或者幾個(gè)月再回頭來(lái)看,也許就有煥然一新的靈感。這樣反反復(fù)復(fù)很多次之后,你就積累起了靈感和智慧,從而能夠在遇到新問(wèn)題的時(shí)候直接朝正確,或者接近正確的方向前進(jìn)。
寫(xiě)優(yōu)雅的代碼
人們都討厭“面條代碼”(spaghetti code),因?yàn)樗拖衩鏃l一樣繞來(lái)繞去,沒(méi)法理清頭緒。那么優(yōu)雅的代碼一般是什么形狀的呢?經(jīng)過(guò)多年的觀察,我發(fā)現(xiàn)優(yōu)雅的代碼,在形狀上有一些明顯的特征。
如果我們忽略具體的內(nèi)容,從大體結(jié)構(gòu)上來(lái)看,優(yōu)雅的代碼看起來(lái)就像是一些整整齊齊,套在一起的盒子。如果跟整理房間做一個(gè)類(lèi)比,就很容易理解。如果你把所有物品都丟在一個(gè)很大的抽屜里,那么它們就會(huì)全都混在一起。你就很難整理,很難迅速的找到需要的東西。但是如果你在抽屜里再放幾個(gè)小盒子,把物品分門(mén)別類(lèi)放進(jìn)去,那么它們就不會(huì)到處亂跑,你就可以比較容易的找到和管理它們。
優(yōu)雅的代碼的另一個(gè)特征是,它的邏輯大體上看起來(lái),是枝丫分明的樹(shù)狀結(jié)構(gòu)(tree)。這是因?yàn)槌绦蛩龅膸缀跻磺惺虑?#xff0c;都是信息的傳遞和分支。你可以把代碼看成是一個(gè)電路,電流經(jīng)過(guò)導(dǎo)線(xiàn),分流或者匯合。如果你是這樣思考的,你的代碼里就會(huì)比較少出現(xiàn)只有一個(gè)分支的if語(yǔ)句,它看起來(lái)就會(huì)像這個(gè)樣子:
if (...) {
if (...) {
...
} else {
...
}
} else if (...) {
...
} else {
...
}
注意到了嗎?在我的代碼里面,if語(yǔ)句幾乎總是有兩個(gè)分支。它們有可能嵌套,有多層的縮進(jìn),而且else分支里面有可能出現(xiàn)少量重復(fù)的代碼。然而這樣的結(jié)構(gòu),邏輯卻非常嚴(yán)密和清晰。在后面我會(huì)告訴你為什么if語(yǔ)句最好有兩個(gè)分支。
寫(xiě)模塊化的代碼
有些人吵著鬧著要讓程序“模塊化”,結(jié)果他們的做法是把代碼分部到多個(gè)文件和目錄里面,然后把這些目錄或者文件叫做“module”。他們甚至把這些目錄分放在不同的VCS repo里面。結(jié)果這樣的作法并沒(méi)有帶來(lái)合作的流暢,而是帶來(lái)了許多的麻煩。這是因?yàn)樗麄兤鋵?shí)并不理解什么叫做“模塊”,膚淺的把代碼切割開(kāi)來(lái),分放在不同的位置,其實(shí)非但不能達(dá)到模塊化的目的,而且制造了不必要的麻煩。
真正的模塊化,并不是文本意義上的,而是邏輯意義上的。一個(gè)模塊應(yīng)該像一個(gè)電路芯片,它有定義良好的輸入和輸出。實(shí)際上一種很好的模塊化方法早已經(jīng)存在,它的名字叫做“函數(shù)”。每一個(gè)函數(shù)都有明確的輸入(參數(shù))和輸出(返回值),同一個(gè)文件里可以包含多個(gè)函數(shù),所以你其實(shí)根本不需要把代碼分開(kāi)在多個(gè)文件或者目錄里面,同樣可以完成代碼的模塊化。我可以把代碼全都寫(xiě)在同一個(gè)文件里,卻仍然是非常模塊化的代碼。
想要達(dá)到很好的模塊化,你需要做到以下幾點(diǎn):
- 避免寫(xiě)太長(zhǎng)的函數(shù)。如果發(fā)現(xiàn)函數(shù)太大了,就應(yīng)該把它拆分成幾個(gè)更小的。通常我寫(xiě)的函數(shù)長(zhǎng)度都不超過(guò)40行。對(duì)比一下,一般筆記本電腦屏幕所能容納的代碼行數(shù)是50行。我可以一目了然的看見(jiàn)一個(gè)40行的函數(shù),而不需要滾屏。只有40行而不是50行的原因是,我的眼球不轉(zhuǎn)的話(huà),最大的視角只看得到40行代碼。
如果我看代碼不轉(zhuǎn)眼球的話(huà),我就能把整片代碼完整的映射到我的視覺(jué)神經(jīng)里,這樣就算忽然閉上眼睛,我也能看得見(jiàn)這段代碼。我發(fā)現(xiàn)閉上眼睛的時(shí)候,大腦能夠更加有效地處理代碼,你能想象這段代碼可以變成什么其它的形狀。40行并不是一個(gè)很大的限制,因?yàn)楹瘮?shù)里面比較復(fù)雜的部分,往往早就被我提取出去,做成了更小的函數(shù),然后從原來(lái)的函數(shù)里面調(diào)用。
- 制造小的工具函數(shù)。如果你仔細(xì)觀察代碼,就會(huì)發(fā)現(xiàn)其實(shí)里面有很多的重復(fù)。這些常用的代碼,不管它有多短,提取出去做成函數(shù),都可能是會(huì)有好處的。有些幫助函數(shù)也許就只有兩行,然而它們卻能大大簡(jiǎn)化主要函數(shù)里面的邏輯。
有些人不喜歡使用小的函數(shù),因?yàn)樗麄兿氡苊夂瘮?shù)調(diào)用的開(kāi)銷(xiāo),結(jié)果他們寫(xiě)出幾百行之大的函數(shù)。這是一種過(guò)時(shí)的觀念。現(xiàn)代的編譯器都能自動(dòng)的把小的函數(shù)內(nèi)聯(lián)(inline)到調(diào)用它的地方,所以根本不產(chǎn)生函數(shù)調(diào)用,也就不會(huì)產(chǎn)生任何多余的開(kāi)銷(xiāo)。
寫(xiě)可讀的代碼
有些人以為寫(xiě)很多注釋就可以讓代碼更加可讀,然而卻發(fā)現(xiàn)事與愿違。注釋不但沒(méi)能讓代碼變得可讀,反而由于大量的注釋充斥在代碼中間,讓程序變得障眼難讀。而且代碼的邏輯一旦修改,就會(huì)有很多的注釋變得過(guò)時(shí),需要更新。修改注釋是相當(dāng)大的負(fù)擔(dān),所以大量的注釋,反而成為了妨礙改進(jìn)代碼的絆腳石。
實(shí)際上,真正優(yōu)雅可讀的代碼,是幾乎不需要注釋的。如果你發(fā)現(xiàn)需要寫(xiě)很多注釋,那么你的代碼肯定是含混晦澀,邏輯不清晰的。其實(shí),程序語(yǔ)言相比自然語(yǔ)言,是更加強(qiáng)大而嚴(yán)謹(jǐn)?shù)?#xff0c;它其實(shí)具有自然語(yǔ)言最主要的元素:主語(yǔ),謂語(yǔ),賓語(yǔ),名詞,動(dòng)詞,如果,那么,否則,是,不是,…… 所以如果你充分利用了程序語(yǔ)言的表達(dá)能力,你完全可以用程序本身來(lái)表達(dá)它到底在干什么,而不需要自然語(yǔ)言的輔助。
有少數(shù)的時(shí)候,你也許會(huì)為了繞過(guò)其他一些代碼的設(shè)計(jì)問(wèn)題,采用一些違反直覺(jué)的作法。這時(shí)候你可以使用很短注釋,說(shuō)明為什么要寫(xiě)成那奇怪的樣子。這樣的情況應(yīng)該少出現(xiàn),否則這意味著整個(gè)代碼的設(shè)計(jì)都有問(wèn)題。
寫(xiě)簡(jiǎn)單的代碼
程序語(yǔ)言都喜歡標(biāo)新立異,提供這樣那樣的“特性”,然而有些特性其實(shí)并不是什么好東西。很多特性都經(jīng)不起時(shí)間的考驗(yàn),最后帶來(lái)的麻煩,比解決的問(wèn)題還多。很多人盲目的追求“短小”和“精悍”,或者為了顯示自己頭腦聰明,學(xué)得快,所以喜歡利用語(yǔ)言里的一些特殊構(gòu)造,寫(xiě)出過(guò)于“聰明”,難以理解的代碼。
并不是語(yǔ)言提供什么,你就一定要把它用上的。實(shí)際上你只需要其中很小的一部分功能,就能寫(xiě)出優(yōu)秀的代碼。我一向反對(duì)“充分利用”程序語(yǔ)言里的所有特性。實(shí)際上,我心目中有一套最好的構(gòu)造。不管語(yǔ)言提供了多么“神奇”的,“新”的特性,我基本都只用經(jīng)過(guò)千錘百煉,我覺(jué)得值得信奈的那一套。
現(xiàn)在針對(duì)一些有問(wèn)題的語(yǔ)言特性,我介紹一些我自己使用的代碼規(guī)范,并且講解一下為什么它們能讓代碼更簡(jiǎn)單。
- 避免使用自增減表達(dá)式(i++,++i,i--,--i)。這種自增減操作表達(dá)式其實(shí)是歷史遺留的設(shè)計(jì)失誤。它們含義蹊蹺,非常容易弄錯(cuò)。它們把讀和寫(xiě)這兩種完全不同的操作,混淆纏繞在一起,把語(yǔ)義搞得烏七八糟。含有它們的表達(dá)式,結(jié)果可能取決于求值順序,所以它可能在某種編譯器下能正確運(yùn)行,換一個(gè)編譯器就出現(xiàn)離奇的錯(cuò)誤。
其實(shí)這兩個(gè)表達(dá)式完全可以分解成兩步,把讀和寫(xiě)分開(kāi):一步更新i的值,另外一步使用i的值。比如,如果你想寫(xiě)foo(i++),你完全可以把它拆成int t = i; i += 1; foo(t);。如果你想寫(xiě)foo(++i),可以拆成i += 1; foo(i); 拆開(kāi)之后的代碼,含義完全一致,卻清晰很多。到底更新是在取值之前還是之后,一目了然。
- 有人也許以為i++或者++i的效率比拆開(kāi)之后要高,這只是一種錯(cuò)覺(jué)。這些代碼經(jīng)過(guò)基本的編譯器優(yōu)化之后,生成的機(jī)器代碼是完全沒(méi)有區(qū)別的。自增減表達(dá)式只有在兩種情況下才可以安全的使用。一種是在for循環(huán)的update部分,比如for(int i = 0; i < 5; i++)。另一種情況是寫(xiě)成單獨(dú)的一行,比如i++;。這兩種情況是完全沒(méi)有歧義的。你需要避免其它的情況,比如用在復(fù)雜的表達(dá)式里面,比如foo(i++),foo(++i) + foo(i),…… 沒(méi)有人應(yīng)該知道,或者去追究這些是什么意思。
- 永遠(yuǎn)不要省略花括號(hào)。很多語(yǔ)言允許你在某種情況下省略掉花括號(hào),比如C,Java都允許你在if語(yǔ)句里面只有一句話(huà)的時(shí)候省略掉花括號(hào):
if (...)
action1();
- 避免使用continue和break。循環(huán)語(yǔ)句(for,while)里面出現(xiàn)return是沒(méi)問(wèn)題的,然而如果你使用了continue或者break,就會(huì)讓循環(huán)的邏輯和終止條件變得復(fù)雜,難以確保正確。
出現(xiàn)continue或者break的原因,往往是對(duì)循環(huán)的邏輯沒(méi)有想清楚。如果你考慮周全了,應(yīng)該是幾乎不需要continue或者break的。如果你的循環(huán)里出現(xiàn)了continue或者break,你就應(yīng)該考慮改寫(xiě)這個(gè)循環(huán)。
另外一種過(guò)度工程的來(lái)源,是過(guò)度的關(guān)心“代碼重用”。很多人“可用”的代碼還沒(méi)寫(xiě)出來(lái)呢,就在關(guān)心“重用”。為了讓代碼可以重用,最后被自己搞出來(lái)的各種框架捆住手腳,最后連可用的代碼就沒(méi)寫(xiě)好。如果可用的代碼都寫(xiě)不好,又何談重用呢?很多一開(kāi)頭就考慮太多重用的工程,到后來(lái)被人完全拋棄,沒(méi)人用了,因?yàn)閯e人發(fā)現(xiàn)這些代碼太難懂了,自己從頭開(kāi)始寫(xiě)一個(gè),反而省好多事。
過(guò)度地關(guān)心“測(cè)試”,也會(huì)引起過(guò)度工程。有些人為了測(cè)試,把本來(lái)很簡(jiǎn)單的代碼改成“方便測(cè)試”的形式,結(jié)果引入很多復(fù)雜性,以至于本來(lái)一下就能寫(xiě)對(duì)的代碼,最后復(fù)雜不堪,出現(xiàn)很多bug。
世界上有兩種“沒(méi)有bug”的代碼。一種是“沒(méi)有明顯的bug的代碼”,另一種是“明顯沒(méi)有bug的代碼”。第一種情況,由于代碼復(fù)雜不堪,加上很多測(cè)試,各種coverage,貌似測(cè)試都通過(guò)了,所以就認(rèn)為代碼是正確的。第二種情況,由于代碼簡(jiǎn)單直接,就算沒(méi)寫(xiě)很多測(cè)試,你一眼看去就知道它不可能有bug。你喜歡哪一種“沒(méi)有bug”的代碼呢?
根據(jù)這些,我總結(jié)出來(lái)的防止過(guò)度工程的原則如下:
- 先把眼前的問(wèn)題解決掉,解決好,再考慮將來(lái)的擴(kuò)展問(wèn)題。
- 先寫(xiě)出可用的代碼,反復(fù)推敲,再考慮是否需要重用的問(wèn)題。
- 先寫(xiě)出可用,簡(jiǎn)單,明顯沒(méi)有bug的代碼,再考慮測(cè)試的問(wèn)題。?
更多Java培訓(xùn),Java視頻,Java教程盡在北京尚學(xué)堂,關(guān)注北京尚學(xué)堂官方微信,獲得一手Java最新知識(shí)。
更多猛料!歡迎掃描上方二維碼關(guān)注北京尚學(xué)堂官方微信公眾號(hào)(資料領(lǐng)取驗(yàn)證消息:156)
本文作者北京尚學(xué)堂原創(chuàng)。如需轉(zhuǎn)載請(qǐng)聯(lián)系作者授權(quán),未經(jīng)授權(quán),轉(zhuǎn)載必究。
?
轉(zhuǎn)載于:https://my.oschina.net/u/2947706/blog/790346
總結(jié)
以上是生活随笔為你收集整理的北京尚学堂|程序员的智慧的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 梦到满屋子老鼠跑什么意思
- 下一篇: IT综合学习网站收集