編程習慣

作者: Alexey Radul  來源: 程序員  發布時間: 2009-10-26 14:49  閱讀: 3206 次  推薦: 0   原文鏈接   [收藏]  

  文/Alexey Radul 譯/程顯峰

  原文地址:http://web.mit.edu/~axch/www/programming_habits.html

  近年來,我對編程藝術有很多體會。過后,我發現有些體會是錯的;有些體會我遺忘了但又重新感受到;而另外有些則是必然會發現的。我還完善了一套項目管理的好習慣,這些習慣包括我自己的,或者小組的,抑或是更大的,公司內部的。一方面,這些習慣對軟件的成功開發是至關重要的(太小或者純粹巧合的不算),另一方面,這些習慣也不是什么高深莫測的東西,較小的篇幅就可以說清楚了,第三,這些習慣都沒有得到應有的重視。所以我把這些寫下來,而你呢,正讀著呢。

  本文包含很多零散的個人建議,有六大塊,各講一個方面。因為建議很多而且相互聯系緊密,所以不太好把他們逐條陳列。這樣寫還有一個好處就是你可以有所挑選的閱讀,把你所知道的部分跳過去,把你想重新思考的部分溫習一下,或者只是簡略的閱讀提綱不深入研究具體內容。

  1 版本控制

  版本控制是一種在開發的過程中對軟件開發的歷史系統地跟蹤的方法。此項任務由版本控制系統完成,如CVS或Subversion。版本系統保持了一個受控編碼的歷史痕跡,提供很多操作:獲得當前版本代碼(通常稱為“檢出”);“提交”修改;“更新”工作拷貝來協調他人的修改。版本控制系統還提供一些其他功能,如用不同的方式檢查代碼的歷史,撤銷更改,回滾到軟件歷史的某個點,解決沖突(兩個人同時對相同的代碼段進行了不同的修改)。

  用過文本編輯器的“撤銷”按鈕,或是用備份文件進行過恢復的人,都能體會到讓電腦記住以前的東西的驚奇效果。而代碼比普通的文件要復雜的多,自然這種功能在開發中就更加重要了。要是你沒有體會過與別人一起開發的樂趣,記住我的話:潛在的混淆和數據的損失是與參與的人數成正比的。因此,版本控制在開發軟件的過程中的作用是不可估量的,如果使用得當的話還會發揮更大的效力。

  那么,怎樣才能用好版本控制呢?

  1. 選用一個優秀的版本控制系統 我現在非常中意Subversion,它不但高效,而且免費。它現在非常流行,并且會變得更加流行。它幾乎不干擾其他開發過程。我并不是說它是完美的,但在你找到更好的之前確實是一個不錯的選擇。
  2. 一開始就要版本化 要是在項目中有些東西要保存在比草稿紙更加正式一點的地方,那么絕對要將其版本化。推論:要是你已經開始了一個沒有版本控制的項目,馬上建立一個,并提交項目。
  3. 就算你單干也要版本化 三個星期后的你將大大有別于今天的你。努力實踐版本控制使得你實現你的意圖的時候非常清晰,要是你忘記了手頭做的什么事情(相信我,你會忘的),那它可有大用處了。此外,你還免費的做了一個備份,看來這么做一點損失都沒有,好處倒不少。
  4. 只要是人類的智慧就要版本化 代碼(肯定是人類的智慧啦),測試用例,編譯腳本,文檔,任務列表,解釋說明,演講稿,想法,需求—只要是經過人的大腦創造出來的一切,都應該記錄在版本控制系統里,除非你有更好的理由將它們放到別處。
  5. 計算機生成的文件就不必了,這樣做只能導致項目出現不一致(如有人提交了源文件,卻忘記生成了有依賴關系的衍生文件)。比較好的做法是讓版本控制系統忽略這些衍生文件,需要的時候再生成就是了。但是萬萬要把能衍生文件的人類原創途徑記錄在案,包括執行生成過程的命令之類的。
  6. 做好日志 好的版本控制系統都會在每一次更改的時候讓你留下日志,目的是解釋一下你對提交的代碼都做了些什么。千萬別忽略這一步,一定要寫,并且好好的寫。
    i. 不光是為別人而寫也是為自己,認真細致記錄日志會迫使你梳理你的設計,看清問題所在,認清正在做的事情,也會使得想知道細節的人(同樣包括未來某時的你)與代碼的作者有個交流。
    ii. 把“做了什么”記下來(必要時補充“原因”),而不是“怎么做的”。要是“怎么做的”真的那么令人感興趣,而且從修改本身很難去理解,當然有必要記一記,但是通常代碼本身已經很能說明“怎么做的”了。要是真的有什么地方不清楚的話,那一定是你的思路。
    iii. 描述點滴所做 版本控制系統能幫助你找到你所做的更改,要試著將所有的修改都詳細的告訴系統。推論:不要做自己都解釋不清的事情。要將其分割成很多比較小的步驟,然后一個一個的來。

  7. 不能搞破壞 每一次你提交了代碼,系統應該是好用的。也就是說,其他人此時更新代碼后應該能夠編譯(可能的話),執行測試套件,并通過測試。將錯誤提交了是對與你協同 的人(還是包括未來的你)極大的不尊重。這會讓他們搞不清楚到底是因為自己的問題還是你提交的一些垃圾導致了系統不能運轉)。

  推論:要是你真的搞破 壞了,要道歉,并立刻修復。

  8. 修改要小到原子 理想的情形下,每一次修改只包含一個意圖,每條日志只是說明一個問題的單句段落。這有一條傻瓜法則用以判斷兩個相互聯系的事情到底是一個還是兩個原子修改:問問自己會不會有人只想撤銷其中一個而保留另一個。如果答案是肯定的,就要分開來提交。

  9.不要改而不交 拖延修改的提交時間越長,你越容易忘了自己都做了什么,越容易產生BUG,也越容易與其它人的相關工作不協調。要是你沒有提交修改,其實一天的活兒就不算干完,除非你有更好的理由。

  2 構建系統
  構建系統是很多自動完成軟件開發任務。其中最常見的就是編譯代碼。運行測試組件也是構建系統提供的功能。還有很多其他的功用,像根據代碼注釋生成可瀏覽的文檔,將組件合并用以發布,等等。將這些工作自動化可以節省很多時間和人力,還會防止有無誤操作或是由于懶惰疏忽而產生的錯誤。

  已經有這樣的工具了。UNIX工具make已經成為構建自動化的標準好多年了,而且相當好用。Ant在Java領域內非常流行,我個人喜歡rake。現代IDE也有部分這樣的功能,或是有調用這些標準工具的接口。無論你最終使用哪一種工具,編譯,測試,或是其它什么別的,應該簡單到只是按一下按鈕。

  構建系統建議:

  1. 使用真正的構建工具 學習一個全新的工具是有些令人卻步,但卻很值得。簡單的腳本總是能搞定你的項目。不會有項目小到不適合應用這些標準工具,只有極少數特大號的項目不適合。
  2. 使一切自動化 編譯和測試應該從一開始就精心地自動化了。同樣當你開始生成文檔或者代碼,做清理,安裝等等的時候,也要使其自動完成。總的來說,要干第二次的任務最好就要自動化。
  3. 自動過程要明晰 尤其要注意,當大家提交東西的時候,應該有一個精心定義的“構建”,一種大家都不應該破壞的“構建”。通常,這會包括成功的編譯結果和測試結果,但是,一定要說清楚哪些命令用來執行,保證其總是能干活。并確保在任意一次執行都能清楚地知道它是否運轉正常。

  3 自動化的測試套件

  測試套件是很多驗證代碼有效的測試。如果其能夠完全由計算機來執行并評估結果,那么就稱其為自動化了的。

  可以將測試按照測試代碼的多少分類:“單元測試”檢驗單一組件的功能,比如一個函數或者一個類。“集成測試”檢驗若干組件,也可能是整個系統是否能協同工作。“功能測試”檢驗系統(通常是全部或大部分)在一個較高的層次上能否正常運轉。后兩個概念有些重疊,但是從不同的角度,和我個人的經驗來說,還沒有一個統一的說法到底如何區分。這兩條術語經常用來區別于單元測試。

  我不會詳細討論優秀自動測試的好處。當前,自動測試問題分得很細:誰應該負責測試,怎樣測試,進行多少測試,以及如何將其自動化,何時創建測試,等等。在 Internet上有很多此項議題的討論,我就不浪費大家的時間在這里啰嗦了。肯定的講,我個人覺得良好的測試套件,包括足夠的單元測試和集成測試(功能測試),對任何項目都是非常重要的。這些應該與主要代碼同步完成,還必須是同樣的人(要是項目很大能請得起專業的測試人員幫助他們那就更好了)。

  已經有一些用于編寫測試組件的工具了,Java有JUnit,Python 有PyUnit, Ruby有Test::Unit, 等等實際上每一種編程語言都有xUnit風格的測試庫,絕大部分還提供別的方法。不要被這么多選擇嚇著,是否使用測試組件比到底用哪種組件重要的多。

  那么,如何建立一個優秀的測試組件呢?

  1. 要提交組件 要知道測試程序也是代碼的一部分,和那些在產品中真正運行的東西一樣重要。將其版本化。像其它部分一樣,共享,備份,跟蹤都要進行。另外,所有人都要使用相同的組件。
  2. 測試組件要自動化 應該有一個清晰的按鈕(比如說‘make test’命令)可以執行所有的測試,并得出結果報告。
  3. 測試組件應該是清晰明確的 運行了測試之后,通沒通過應該一目了然,如果沒通過,那一部分沒通過也應該顯而易見。絕對不要參砸任何的人為判定到底是通過了還是沒通過。測試也不要產生任何可能會使報告費解的輸出。
  4. 一定要通過測試提交未通過測試的東西無異于對“構建”進行破壞,是要絕對禁止的(如果不巧發生了,要立即修復)。如果你知道代碼是正確無誤的,是測試出了問題,那就要修改測試。如果你的確要提交,但是有些測試就是毫無道理的通不過,你也沒時間立即debug,那么暫時將其從組件中去掉,過后要馬上弄好。
  5. 經常測試 修改的時候要測,提交前要測,開發期間要測。運行測試套件(至少也是其中的相當的一部分)應該成為你開發周期的一部分。
  6. 測試先行 不要驚訝,我是說真的。當修正bug的時候,先要寫一個能針對它的測試,然后再修復bug。要是測試通過了,就成了。添加新的功能的時候,先寫好針對它的測試。不僅會幫助你理解這個功能到底應該干什么,還會在干成了的時候通知你。
  7. 測試是可以執行的文檔 與普通文檔不同,測試從不說謊,因為人人都運行還總得通過。如果你覺得一段代碼難于理解,那么就寫一個單元測試。如果你寫了實際的文檔,寫寫測試來驗證一下其中的陳述。
  8. 依靠閱讀來測試代碼能否工作顯然是行不通的,所以還是寫測試來檢驗到底是否正常運轉。你將會驚訝地發現找到很多bug,也會驚訝于避免了相當多的bug產生。
  9. 只做有效的測試很容易寫如下的測試:“當我運行這個程序時給這個輸入,就會產生10,000行我這個文件里的輸出”。通常這樣做比沒有好,但絕對是一個糟糕的測試,因為除了真正起作用的東西外,也測試了許許多多無關的細節(如浮點舍入誤差)。改進類似的測試來適應程序中的修改,要么極其痛苦,要么很可能引入bug。

  4 代碼審核

  代碼審核是通過閱讀別人的代碼,來尋找錯誤,提出改進意見等的過程和實踐。使得代碼更清晰,設計更合理,綜合性能更好是條很漫長的路。另外一雙眼睛,另外一種審視問題的角度會極大的促進方案更清楚,更優異。代碼審核還會幫助程序員相互傳授有用的技術,方法,風格等。只要一個項目的開發者多于一個人,那么立即開始相互審核代碼。理想情況下,每一行代碼都要被兩個人看過,作者和審核人。
  
  對代碼審核的實踐有很多東西值得探討:何時審核,如何整體審核,由誰來審核。在很多全職的程序員共同參與的大工程中,每一段代碼都應在提交的時候送評(如果有更好的工具支持,則應在提交之前)。審核人要清楚與之相關的代碼,并要理解其作用(還有,更重要的,可能會發生的錯誤)。在此過程中,審核要快速的完成(一到兩天內),作者在評論完成之前,不要試圖改動這段代碼,以避免把事情搞亂。

  參與人數少的小項目則不必如此。要是代碼不多的話,進展的速度會很快,要是每一次提交都審核的話耽誤不起。但是,代碼審核對代碼和寫代碼的人都很有益處(因為,不說別的,這使得至少兩個人都理解這段代碼。)用什么樣的風格做項目,只要適合就好,但一定要養成審核代碼和讓人審核代碼的好習慣。

  如果你是審核人:

  1. 及時 如若有人請你審核一段代碼,要么馬上開始,要么痛快的告訴人家不行(并轉交給更有資歷的審核人)。千萬不要讓人家等。
  2. 尊重 代碼審核的目的是確保代碼的質量,不是為了顯示出誰比誰高明。作為審核人,你的確有很大的權力,但是不能亂用。
  3. 詳盡 若是有些東西你搞不清楚,要么是代碼寫的就不夠清楚,要么是注釋不夠,也可能二者兼有。請作者澄清(不單單是對你私下里的解釋,要寫進代碼里)。要是什么不對勁,這很可能,或者是對的但看上去是錯的,或是晦澀的,都要告訴作者使其修改。
  4. 執行規定 要是項目中有些規定或規范(代碼風格,命名規范,測試等),發現不符的地方一定要讓作者修改。有些時候這樣做顯得吹毛求疵,但是定下這些規定和規范是有緣由的,所以應該遵守。

  如果你是被審核代碼的作者:

  1. 尊重 審核人是你的朋友,給你有價值的建議。如果你有些不同見解,那么這絕對是建設性談話的好議題。要是審核人對你的代碼有誤解,那么極有可能是你沒有把代碼寫清楚。
  2. 不要以為批評是針對你本人 代碼審核是要改進代碼,不是要刺傷(或者是提升)你的自尊心。審核之所以一定要關注你做錯的地方,是因為那就是要改進的地方。對不良代碼的批評都是建設性的(很可能包括積極的建議從某方面深入思考),要是并非如此,也許找審核人禮貌的談一談是個不錯的選擇。
  3. 盡早盡量獲得審核審核一大堆剛寫的代碼是非常非常討厭的,只有一種例外,審核人發現在整個部分你都在犯傻。這個過程需要一點一點修改提交。即使你認為要多做一點兒,將工作拆分成易于審核的小塊,這樣會避免很多錯誤,以使你得到數倍的回報。十個100行的代碼審核要比一個900行的來得輕松得多,拆分著做會節省很多尋找修正 bug的工作量。

  5 重構

  重寫一段代碼保留其運行的外部特性,但在某些方面有所改進,這樣的過程稱之為重構。通常這是為了代碼更加清晰易讀,或是為了更易于擴展,也可能是為了執行的更快。這項活動不論規模大小,都可以叫做重構。重新命名變量和函數是重構,在類之間調換功用亦是重構,將一個100,000行糾纏在一起的代碼分解成一個插件架構和若干相對較小獨立的組件。當然,三種目的的重構遇到的問題會有很大的不同,但有一點是不變的:無論哪一種目的的重構都不改變原有代碼與外部世界的交互。

  重構對代碼,對身心都是有益處的。一定要重構。重寫一段代碼并不丟人。就當第一版是草稿。這會幫助你勾勒問題的輪廓,是你明白你要從方案中得到些什么。得到已經正常運轉的代碼是非常有用的,這樣可以制定測試組件,來精確定義你到底要解決什么問題。然后,有了測試組件,你就可以修改你的初始方案做出改進。之后,修改第二稿,第三稿,直到沒有可改的了。相似的,千萬不要以為沒有對代碼的行為做出顯式的修改就是浪費時間。相反,通過梳理代碼,你可以使其更加易讀便于理解,易于維護和擴展,便于發現和修正bug。要是需求在最后的時刻發生了變化,在這上面花點精力的就會顯得太值了,事實上也往往如此。

  那么,怎么重構呢:

  1. 重構 其價值如何強調都不過分。
  2. 不要將重構與實質性的修改混為一談 代碼的行為在你重構前后應該是一致的,相同的。這也是檢查與驗證你是不是進行重構的極其有效的手段。盡可能將你的重構一個一個的提交,而將實質性的修改分開提交。
  3. 測試重構的代碼 除了最簡單重構外,值得重構的代碼,也只得測試。相應地,由重構的定義可知重構應該在不改變代碼行為的框架內進行。若是測試套件已經有了足夠的邊界條件測試,很好。要是沒有,做一些,并在你開始重構之前弄好它們。這是一個很好的方法可以使你的重構不拖累其它部分。
  4. 一小塊一小塊的來信誓旦旦地說“我要重寫這個程序”,然后埋頭就干,企圖一下子把所有都重做一遍。說說很容易,但絕對行不通。這樣絕對不會得到看上去一樣的程序,你就開始想是個bug呢,還是原本特性如此呢,原來的代碼到底對此事如何處理呢,等等。絕對是災難。在重構過程中,當目標總是停滯不前,我們總是可是把事情分成一小段一小段的,以便查驗。
  5. 別害怕丑陋的中間產物舉例來說,如果你想修改一個接口,首先為新的接口提供支持,然后將所有客戶端逐個轉換,然后去掉舊版本的接口。要是修改過于龐大的話,不要一蹴而就,將其拆分成幾個小部分完成。可以肯定當你修改到一半時,代碼看起來會很難看,有些客戶端用新方法,有些用舊方式,但是程序依舊正常工作,測試也正常通過,你好檢驗你的修改沒有引入任何不希望的破壞。重構都可以像這樣來拆分。嘗試拆分,盡量將工作化成可以度量的小塊兒。
  6. 不要卡在中間 要是開始了進行重構,那就進行到底。關鍵是代碼要變清晰,但是干了一半兒的活兒通常會是舊的混亂加上新的方案混合在一起。絕對不要讓程序長時間出于這種狀態。
  7. 通過重構為實質性修改鋪墊你是不是經常開始做X的時候發現必須要先改Y才能行,而在改Y的時候,你又發現得先改Z,最后呢你困惑了,結果是一團糟。我們都有過類似的經歷,只是相對少一些而已,預先估計都需要改什么,然后先重構,測試,提交Z,接著是Y,最后是X。這樣會得到更好的代碼,減少錯誤,即使真的有東西弄亂了,你也更容易讓其步入正軌。
  8. 別怕刪代碼人們(很可能是你)花了很多的時間去寫并不意味著是代碼一定能很好地完成既定任務。這些代碼幫助定義了任務是什么,完成此項任務會遇到怎樣的困難,這些都對日后的工作很有用,因此其作者的工作很值得尊敬。即使是知道了不用這么做也是很有幫助的,有時候很值得投入全部力量去寫代碼做這項似乎無意義的事。另一方面,無效的,廢棄的代碼只會使程序變得擁擠不堪,難于閱讀和理解,所以還是刪掉的好。要是后來你又最終決定用了,版本控制系統會將其完好無損的還給你。
  9. 不要把應刪掉的代碼注釋掉了事我知道有些人重寫提交代碼的時候,把舊代碼注釋掉。千萬不要這樣。將代碼注釋掉不僅沒有任何幫助而且非常容易造成混亂。如果你是想有一個備份用來恢復,那么記住版本控制系統做這個更加在行。如果你是想解釋新代碼應該做什么,那么還是用英語解釋更合適。不管你的目的是什么,注釋代碼都不是個好主意。

  6 代碼風格

  代碼風格是指在寫程序的時候我們做出的很多細微,不關緊要,不加以思考的選擇。一個子塊要縮進多少?if語句里的條件加括號了么?只有一行的循環加花括號了么?“+”號和加數之間有空格么?是把代碼用括號圈到一處還是有點縮進?一行可以有多長?這些都是微不足道的事情,但是做好這些會使程序變得非常明晰。

  已經有針對這些的工具了。這些工具程序審查代碼,驗證其是否符合某種風格規范,還能修改源程序以其符合風格規范。我自己從來沒有用過這些工具,也沒有在項目中用過,所以我說不好其價值,但是我要是不提及其存在就是我的不對了。不論你使不使用這種工具,下面的建議都有幫助。

  1. 找到一種代碼風格 不被編譯器或解釋器重視的微小部分應該至始至終的保持一致。沒必要一開始就將其寫下來,但是一定要確保每一個人都知道。隨著項目的增長,要形成書面的文檔。
  2. 認同這種代碼風格 在項目初期,因為項目太小了一個人就可能改變風格,這時禮貌的和富有建設性的討論就很有好處了。要是有明顯更好的途徑,絕對沒有理由堅持一開始的老路。另一方面,不要在此方面浪費太多時間。繼續做要做的決定,并在項目中實施。
  3. 尊重編程語言的代碼風格 要是你所使用的編程語言的社區已經有某種代碼風格習慣,一定要遵循。這么做會使得項目之間相互收益,就像同一種代碼風格會對一個項目本身有好處一樣。
  4. 遵守這個代碼風格 即使你個人偏好在同一行開始一個花括號,要是項目要求其獨立成行,還是按著做吧。一致性,和由此而來的穩定性比個人的口味更加重要。
  5. 保持局部一致 如果你要修改的文件或組件不符合全局的代碼風格,應該有人將其轉換,但是如果你當時沒有這個時間,那么遵循你要修改代碼周圍的代碼風格。局部一致比全局一致更加重要。

  7 結束語

  如果上面說的東西對你來說挺新鮮的,我鼓勵你將其融入到你的工作中去。也許要花點時間習慣,但是相信我,很值得。要是你在學習這些,我建議寧火不溫。這是因為從定義上來講,對某一概念缺乏了解會桎梏實踐,所以對一件好事來講,只有當你做過了頭,你才能充分了解整個問題的尺度。只有當你既知道這是個好事并了解你已經做過頭了,你才會對什么是適量做出正確的判斷。

  8 注釋

  1. 要是你的工作拷貝完蛋了,你還有版本庫,所以你還是能弄到一份工作拷貝;要是你的版本庫完蛋了,就算你沒有備份它,你還是有一份(或多份)工作拷貝,你還是可以恢復當前狀態,并接著干活。不過呢,備份版本庫才是好的方式,這樣你就可以連同項目的歷史都能恢復出來。
  2. 許多編程環境是允許提交試驗性代碼的,這種代碼除了提交者本人外不會影響任何人。這樣的代碼不屬于構建,所以就算提交了殘缺的試驗性代碼也不能破壞構建本身。當然有些情形下這樣做是必須的,但是我必須要警告大家不要濫用:避免構建框架的束縛的同時,也喪失了構建框架的好處。
  3. rake和make差不多,它用Ruby作為描述語言,比較新,但有些復雜的功能尚不具備。
  4. 要是你的項目增長的比工具的能力還迅速,那么你正應該考慮雇傭一名專門的構建工程師來定制一個適用的系統,不過那是公司的事兒,不是個人的。
  5. 這種風格問題是Lisp程序員提出來的,并困擾了很多其他語言程序員很長時間。

  9 致謝

  作者感謝Gregory Marton, Tanya Khovanova和Arthur Gleckler在成稿期間的評論。譯者感謝blade和wangyao的細心核對和寶貴建議。

0
0
 
標簽:編程習慣
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()