北京尚學堂|程序員的智慧

版權聲明:本文爲北京尚學堂原創文章,未經容許不得轉載。html

 

編程是一種創造性的工做,是一門藝術。精通任何一門藝術,都須要不少的練習和領悟,因此這裏提出的「智慧」,並非號稱一天瘦十斤的減肥藥,它並不能代替你本身的勤奮。然而因爲軟件行業喜歡標新立異,喜歡把簡單的事情搞複雜,我但願這些文字能給迷惑中的人們指出一些正確的方向,讓他們少走一些彎路,基本作到一分耕耘一分收穫。程序員

反覆推敲代碼編程

既然「天才是百分之一的靈感,百分之九十九的汗水」,那我先來談談這汗水的部分吧。有人問我,提升編程水平最有效的辦法是什麼?我想了好久,終於發現最有效的辦法,實際上是反反覆覆地修改和推敲代碼。安全

在IU的時候,因爲Dan Friedman的嚴格教導,咱們以寫出冗長複雜的代碼爲恥。若是你代碼多寫了幾行,這老頑童就會大笑,說:「當年我解決這個問題,只寫了5行代碼,你回去再想一想吧……」 固然,有時候他只是誇張一下,故意刺激你的,其實沒有人能只用5行代碼完成。然而這種提煉代碼,減小冗餘的習慣,卻由此深刻了個人骨髓。微信

有些人喜歡炫耀本身寫了多少多少萬行的代碼,彷彿代碼的數量是衡量編程水平的標準。然而,若是你老是匆匆寫出代碼,卻歷來不回頭去推敲,修改和提煉,實際上是不可能提升編程水平的。你會製造出愈來愈多平庸甚至糟糕的代碼。在這種意義上,不少人所謂的「工做經驗」,跟他代碼的質量,其實不必定成正比。若是有幾十年的工做經驗,卻歷來不回頭去提煉和反思本身的代碼,那麼他也許還不如一個只有一兩年經驗,卻喜歡反覆推敲,仔細領悟的人。框架

有位文豪說得好:「看一個做家的水平,不是看他發表了多少文字,而要看他的廢紙簍裏扔掉了多少。」 我以爲一樣的理論適用於編程。好的程序員,他們刪掉的代碼,比留下來的還要多不少。若是你看見一我的寫了不少代碼,卻沒有刪掉多少,那他的代碼必定有不少垃圾。模塊化

就像文學做品同樣,代碼是不可能一蹴而就的。靈感彷佛老是零零星星,陸陸續續到來的。任何人都不可能一筆呵成,就算再厲害的程序員,也須要通過一段時間,才能發現最簡單優雅的寫法。有時候你反覆提煉一段代碼,以爲到了頂峯,無法再改進了,但是過了幾個月再回頭來看,又發現好多能夠改進和簡化的地方。這跟寫文章如出一轍,回頭看幾個月或者幾年前寫的東西,你總能發現一些改進。函數

因此若是反覆提煉代碼已經再也不有進展,那麼你能夠暫時把它放下。過幾個星期或者幾個月再回頭來看,也許就有面目一新的靈感。這樣反反覆覆不少次以後,你就積累起了靈感和智慧,從而可以在遇到新問題的時候直接朝正確,或者接近正確的方向前進。工具

寫優雅的代碼測試

人們都討厭「麪條代碼」(spaghetti code),由於它就像麪條同樣繞來繞去,無法理清頭緒。那麼優雅的代碼通常是什麼形狀的呢?通過多年的觀察,我發現優雅的代碼,在形狀上有一些明顯的特徵。

若是咱們忽略具體的內容,從大致結構上來看,優雅的代碼看起來就像是一些整整齊齊,套在一塊兒的盒子。若是跟整理房間作一個類比,就很容易理解。若是你把全部物品都丟在一個很大的抽屜裏,那麼它們就會全都混在一塊兒。你就很難整理,很難迅速的找到須要的東西。可是若是你在抽屜裏再放幾個小盒子,把物品分門別類放進去,那麼它們就不會處處亂跑,你就能夠比較容易的找到和管理它們。

優雅的代碼的另外一個特徵是,它的邏輯大致上看起來,是枝丫分明的樹狀結構(tree)。這是由於程序所作的幾乎一切事情,都是信息的傳遞和分支。你能夠把代碼當作是一個電路,電流通過導線,分流或者匯合。若是你是這樣思考的,你的代碼裏就會比較少出現只有一個分支的if語句,它看起來就會像這個樣子:

if (...) {

if (...) {

...

} else {

...

}

} else if (...) {

...

} else {

...

}

注意到了嗎?在個人代碼裏面,if語句幾乎老是有兩個分支。它們有可能嵌套,有多層的縮進,並且else分支裏面有可能出現少許重複的代碼。然而這樣的結構,邏輯卻很是嚴密和清晰。在後面我會告訴你爲何if語句最好有兩個分支。

寫模塊化的代碼

有些人吵着鬧着要讓程序「模塊化」,結果他們的作法是把代碼分部到多個文件和目錄裏面,而後把這些目錄或者文件叫作「module」。他們甚至把這些目錄分放在不一樣的VCS repo裏面。結果這樣的做法並無帶來合做的流暢,而是帶來了許多的麻煩。這是由於他們其實並不理解什麼叫作「模塊」,膚淺的把代碼切割開來,分放在不一樣的位置,其實非但不能達到模塊化的目的,並且製造了沒必要要的麻煩。

真正的模塊化,並非文本意義上的,而是邏輯意義上的。一個模塊應該像一個電路芯片,它有定義良好的輸入和輸出。實際上一種很好的模塊化方法早已經存在,它的名字叫作「函數」。每個函數都有明確的輸入(參數)和輸出(返回值),同一個文件裏能夠包含多個函數,因此你其實根本不須要把代碼分開在多個文件或者目錄裏面,一樣能夠完成代碼的模塊化。我能夠把代碼全都寫在同一個文件裏,卻仍然是很是模塊化的代碼。

想要達到很好的模塊化,你須要作到如下幾點:

- 避免寫太長的函數。若是發現函數太大了,就應該把它拆分紅幾個更小的。一般我寫的函數長度都不超過40行。對比一下,通常筆記本電腦屏幕所能容納的代碼行數是50行。我能夠一目瞭然的看見一個40行的函數,而不須要滾屏。只有40行而不是50行的緣由是,個人眼球不轉的話,最大的視角只看獲得40行代碼。

若是我看代碼不轉眼球的話,我就能把整片代碼完整的映射到個人視覺神經裏,這樣就算突然閉上眼睛,我也能看得見這段代碼。我發現閉上眼睛的時候,大腦可以更加有效地處理代碼,你能想象這段代碼能夠變成什麼其它的形狀。40行並非一個很大的限制,由於函數裏面比較複雜的部分,每每早就被我提取出去,作成了更小的函數,而後從原來的函數裏面調用。

- 製造小的工具函數。若是你仔細觀察代碼,就會發現其實裏面有不少的重複。這些經常使用的代碼,無論它有多短,提取出去作成函數,均可能是會有好處的。有些幫助函數也許就只有兩行,然而它們卻能大大簡化主要函數裏面的邏輯。

有些人不喜歡使用小的函數,由於他們想避免函數調用的開銷,結果他們寫出幾百行之大的函數。這是一種過期的觀念。現代的編譯器都能自動的把小的函數內聯(inline)到調用它的地方,因此根本不產生函數調用,也就不會產生任何多餘的開銷。

寫可讀的代碼

有些人覺得寫不少註釋就可讓代碼更加可讀,然而卻發現事與願違。註釋不但沒能讓代碼變得可讀,反而因爲大量的註釋充斥在代碼中間,讓程序變得障眼難讀。並且代碼的邏輯一旦修改,就會有不少的註釋變得過期,須要更新。修改註釋是至關大的負擔,因此大量的註釋,反而成爲了妨礙改進代碼的絆腳石。

實際上,真正優雅可讀的代碼,是幾乎不須要註釋的。若是你發現須要寫不少註釋,那麼你的代碼確定是含混晦澀,邏輯不清晰的。其實,程序語言相比天然語言,是更增強大而嚴謹的,它其實具備天然語言最主要的元素:主語,謂語,賓語,名詞,動詞,若是,那麼,不然,是,不是,…… 因此若是你充分利用了程序語言的表達能力,你徹底能夠用程序自己來表達它到底在幹什麼,而不須要天然語言的輔助。

有少數的時候,你也許會爲了繞過其餘一些代碼的設計問題,採用一些違反直覺的做法。這時候你可使用很短註釋,說明爲何要寫成那奇怪的樣子。這樣的狀況應該少出現,不然這意味着整個代碼的設計都有問題。

寫簡單的代碼

程序語言都喜歡標新立異,提供這樣那樣的「特性」,然而有些特性其實並非什麼好東西。不少特性都經不起時間的考驗,最後帶來的麻煩,比解決的問題還多。不少人盲目的追求「短小」和「精悍」,或者爲了顯示本身頭腦聰明,學得快,因此喜歡利用語言裏的一些特殊構造,寫出過於「聰明」,難以理解的代碼。

並非語言提供什麼,你就必定要把它用上的。實際上你只須要其中很小的一部分功能,就能寫出優秀的代碼。我一貫反對「充分利用」程序語言裏的全部特性。實際上,我心目中有一套最好的構造。無論語言提供了多麼「神奇」的,「新」的特性,我基本都只用通過千錘百煉,我以爲值得信奈的那一套。

如今針對一些有問題的語言特性,我介紹一些我本身使用的代碼規範,而且講解一下爲何它們能讓代碼更簡單。

- 避免使用自增減表達式(i++,++i,i--,--i)。這種自增減操做表達式實際上是歷史遺留的設計失誤。它們含義蹊蹺,很是容易弄錯。它們把讀和寫這兩種徹底不一樣的操做,混淆纏繞在一塊兒,把語義搞得烏七八糟。含有它們的表達式,結果可能取決於求值順序,因此它可能在某種編譯器下能正確運行,換一個編譯器就出現離奇的錯誤。

其實這兩個表達式徹底能夠分解成兩步,把讀和寫分開:一步更新i的值,另一步使用i的值。好比,若是你想寫foo(i++),你徹底能夠把它拆成int t = i; i += 1; foo(t);。若是你想寫foo(++i),能夠拆成i += 1; foo(i); 拆開以後的代碼,含義徹底一致,卻清晰不少。到底更新是在取值以前仍是以後,一目瞭然。

- 有人也許覺得i++或者++i的效率比拆開以後要高,這只是一種錯覺。這些代碼通過基本的編譯器優化以後,生成的機器代碼是徹底沒有區別的。自增減表達式只有在兩種狀況下才能夠安全的使用。一種是在for循環的update部分,好比for(int i = 0; i < 5; i++)。另外一種狀況是寫成單獨的一行,好比i++;。這兩種狀況是徹底沒有歧義的。你須要避免其它的狀況,好比用在複雜的表達式裏面,好比foo(i++),foo(++i) + foo(i),…… 沒有人應該知道,或者去追究這些是什麼意思。

- 永遠不要省略花括號。不少語言容許你在某種狀況下省略掉花括號,好比C,Java都容許你在if語句裏面只有一句話的時候省略掉花括號:

if (...)

action1();

- 避免使用continue和break。循環語句(for,while)裏面出現return是沒問題的,然而若是你使用了continue或者break,就會讓循環的邏輯和終止條件變得複雜,難以確保正確。

出現continue或者break的緣由,每每是對循環的邏輯沒有想清楚。若是你考慮周全了,應該是幾乎不須要continue或者break的。若是你的循環裏出現了continue或者break,你就應該考慮改寫這個循環。

另一種過分工程的來源,是過分的關心「代碼重用」。不少人「可用」的代碼還沒寫出來呢,就在關心「重用」。爲了讓代碼能夠重用,最後被本身搞出來的各類框架捆住手腳,最後連可用的代碼就沒寫好。若是可用的代碼都寫很差,又何談重用呢?不少一開頭就考慮太多重用的工程,到後來被人徹底拋棄,沒人用了,由於別人發現這些代碼太難懂了,本身從頭開始寫一個,反而省好多事。

過分地關心「測試」,也會引發過分工程。有些人爲了測試,把原本很簡單的代碼改爲「方便測試」的形式,結果引入不少複雜性,以致於原本一下就能寫對的代碼,最後複雜不堪,出現不少bug。

世界上有兩種「沒有bug」的代碼。一種是「沒有明顯的bug的代碼」,另外一種是「明顯沒有bug的代碼」。第一種狀況,因爲代碼複雜不堪,加上不少測試,各類coverage,貌似測試都經過了,因此就認爲代碼是正確的。第二種狀況,因爲代碼簡單直接,就算沒寫不少測試,你一眼看去就知道它不可能有bug。你喜歡哪種「沒有bug」的代碼呢?

根據這些,我總結出來的防止過分工程的原則以下:

- 先把眼前的問題解決掉,解決好,再考慮未來的擴展問題。

- 先寫出可用的代碼,反覆推敲,再考慮是否須要重用的問題。

- 先寫出可用,簡單,明顯沒有bug的代碼,再考慮測試的問題。​

更多Java培訓,Java視頻,Java教程盡在北京尚學堂,關注北京尚學堂官方微信,得到一手Java最新知識。

更多猛料!歡迎掃描上方二維碼關注北京尚學堂官方微信公衆號(資料領取驗證消息:156)

本文做者北京尚學堂原創。如需轉載請聯繫做者受權,未經受權,轉載必究。

相關文章
相關標籤/搜索