清理代碼的閱讀筆記

來源:互聯網
上載者:User

標籤:

 

 

深度解析:清理爛代碼

2015-10-05 PHP開發人員

(點擊上方公眾號,可快速關注)

 

英文:Niklas Frykholm

伯樂線上 - 唐小娟

網址:http://blog.jobbole.com/28672/

 

猜猜看怎麼了!你正”繼承“(接收)了一堆混亂的舊代碼。恭喜你!現在都是你的了。混亂的代碼可能來自任何地方。中介軟體,網路,可能來自你自己的公司。

 

你知道在一個角落裡有一個傢伙,沒有人過去管他在做什麼。猜猜看他一直在做什嗎?辛辛苦苦寫出了代碼,卻是一堆爛代碼。

 

你還記得這個模組是一個傢伙幾年前寫的,在他離開公司之前。這個模組已經有20個不同的人加過補丁,進行過代碼修複,而且他們也並不理解代碼到底是做了什麼。是的,就是這樣的代碼。

 

或者你從網上下載下的開源的軟體,你知道它非常的可怕,但是它解決了一個非常專的並且對你來說非常棘手的問題,解決這個問題你可能要花上幾年。

 

爛代碼不一定是問題,只要它們沒有出錯,沒有人會對它嗤之以鼻。但不幸的是,它們沒被發現的機率太小了。錯誤會被發現。需要新的功能,新系統發布了。現在你不得不面對這堆恐怖的代碼,試著去清理它們。這篇文章為這種不幸的情況提供了一些建議。

 

 

0. 值得清理嗎?

 

第一件你需要問問自己的事情就是代碼值得清理麼。我不是說當問到是否要清理代碼時,你一定要回答是或者一定回答不是。是你對代碼負有責任,也是你需要一直面對它們直到最終寫出的代碼是你樂意維護的,也是你很自豪的放入程式碼程式庫的。

 

如果你覺得就算代碼看起來很可怕,也不值得浪費你本來就很緊張的時間,來修複它們。所以你僅僅做了最最微小的調整解救燃眉之急。

 

換句話說,你也可以將代碼看作自己的,也可以看作是別人的。

 

兩種情況都有優缺點。優秀的程式員看到爛代碼時會覺得很難受。他們會拿出火把和叉子並且高呼:“太亂了,太亂了”。這是一種優秀的品質。

 

但是清理代碼是一個繁雜的工作。很容易就低估了時間。甚至有時候和從頭開始寫代碼一樣的耗時。並且短期並沒有帶來任何的短期效應。

兩個星期的時間清理代碼並不會帶來任何新的功能,但有可能引入一些新的錯誤。

 

另一方面,如果長時間不清理代碼可能會帶來災難性的毀滅。混亂是代碼的殺手。

 

如何權衡?

 

所以,這並不是一個容易做出的決定。需要考慮一些事情:

 

你期望對這段代碼做多少改變?你是希望僅僅修改這個小錯誤呢,還是這段代碼還要使用多次,所以你希望將它“調教”的好些,並且加上新的功能。如果僅僅是修複 一個錯誤,那麼最好是別打草驚蛇。然而,如果這個模組你需要長期折騰的話,那麼現在開始花點時間來清理它吧,之後會省掉很多煩惱。

 

 

你需要或者是你想引入上遊的更新嗎?它是一個正在開發當中的開源項目嗎?如果是的話,並且你想做改變的是上遊的代碼,那麼你不能對代碼有大的改動否則當你每次pull代碼的時候都會經曆一場merge的噩夢。所以你需要做一個友好的團隊合作者,接受這個錯誤,將帶有你修正的代碼補丁發給代碼的維護者。

 

 

要做多少工作?你一天內實際上能清理多少行代碼?我們估計多於100行,少於1000行,好,我們假設是1000行。所以如果一個模組有30,000行代碼的話,你可能需要一個月的時間。你有那麼多時間嗎?值得這麼做嗎?

 

 

它是你核心的功能嗎?如果這個模組只是邊緣的模組,譬如字型渲染或者映像渲染,你可能並不在意它是否是亂七八糟的。你可能全盤不要,將來用另外的東西來代替,誰知道呢。如果這段代碼關乎核心的效能,你需要謹慎對待。

 

這段代碼有多糟糕?如果代碼僅僅有一點點糟糕,那麼可能你還是可以忍受的。如果它是不可理喻的,令人崩潰的話,那麼我們就必須對它下手了。

 

 

1. 建立測試案例

 

要認真清理一段代碼意味著花一段時間來徹底清理它。你可能會毀壞它們。

 

如果你有一個比較好的測試案例,有一定的覆蓋率,你將會很容易知道什麼已經損壞了,並且你能夠很快的知道你犯了什麼愚蠢的錯誤。想要節省建立測試案例的時間在整個的清理代碼的過程中是可笑的。建立測試案例吧。這是你第一件需要做的事情。

 

單元測試是最好的,但是所有的代碼並不適應單元測試。如果單元測試過於繁瑣,就換用整合測試吧。譬如,一個遊戲關卡中需要一個人物完成一系列的動作和你清理的代碼有關。

 

這 樣的測試更加耗時,所以不可能在每一次更改之後都測試一次,雖然這是最理想的情況。因為你將每一次改變都放到了版本控制系統中,所以情況還不是那麼糟糕。 所以每一段時間(比如,五個更改)就測試一次。當你發現了一個問題時,你可以通過二進位搜尋最近的幾次commit中找到什麼地方導致了問題的發生。

 

如果你發現了測試沒有發現的問題,確保將這個也加入到測試中,以便將來可以測試它。

 

2. 使用代碼版本控制系統

 

還有人需要被告知要使用代碼版本控制系統嗎?我希望沒有。

 

清理工作是很關鍵的。你可能要做很多很多小的修改。如果什麼地方出錯了,你想回顧版本曆史,你可能找到它錯在哪。

 

如果你和我一樣,你可能有時重構(清理愚蠢的類)的時候會出錯,並且後來意識到這並不是個好的點子,或者這是個好點子,但是如果先做了什麼之後所有的一切會變得更簡單。所以你想快速的恢複一切到原狀並且重新開始。

 

你的公司應該已經有代碼控制系統了,你可以在不同的分支進行修改,在不打擾別人的情況下隨意的commit。

 

就算情況不是這樣的,你也應該使用版本控制。下載Mercurial(或Git),建立新的倉庫,將代碼從你們公司的愚蠢的系統中籤出並放在這裡。在庫中commit你的更改。當你完成了之後你可以將所有的一切merge到那愚蠢的系統中。

 

 

拷貝庫到一個代碼控制系統中僅僅需要幾分鐘。很值得這麼做。如果你不懂Mercurial,花一個小時學習它。你會為你這麼做感到高興的。如果你願意的話,花30個小時學習下Git(我是開玩笑的!並不用這麼久。現在是“nerd”戰鬥的時候了!)

 

 

3. 每次僅僅做一個小小的改動

 

有兩種方法改進壞的代碼:革命和改革。革命是用火把一切都燒掉,從新寫一遍。改革是在不破壞的基礎上每次只進行一點小小的改變。

 

這篇文章是關於改革的方法。我不是說革命的方法從來不是必要的。有時代碼太糟糕了,需要用革命的方法。但是那些覺得改革的進度太慢的人們往往會鼓勵改革,然而經常沒有意識到問題的複雜性,並最終並沒有比現存的系統更好。

 

Joel Spolsky寫過一篇經典的文章,他沒有掉入到這個緊張的爭論的陷阱中。

 

 

改革的最好的方法就是一次只做一個小的改變,測試它,並且commit它。當一個改變很小時,它更容易理解改動的後果以及確保改動不會影響現有的功能。如果什麼地方出錯了,你僅僅需要核查很少的一部分代碼。

 

如果你開始做更改並且意識到改得很糟糕,那麼你恢複到上一次的commit,不會損失太多的無用功。如果你過了一段時間才發現什麼地方有細微的差錯,你可以在版本曆史中使用二進位搜找到導致問題的更改。

 

 

最常見的錯誤就是,一次進行多處更改。譬如,當去除不必要的類層次的勢後,你發現API的方法並不是像你喜歡的使用方法,而你打算重新組織它們。不要這麼做!先去除階層,commit之後再更改API。

 

聰明的程式員懂得組織,所以他們也不需要太聰明。

 

試著找一個途徑,沿著這個途徑你可以把代碼變成你想要的模樣,每次只有一點點改動。譬如,第一步你重新命名方法,使之名字更合理。下一步,你可以將成員變數變成方法的參數。然後將演算法變得更清楚些,等等。

 

如果你開始做更改,並且發現比你原先設想的改變要大,不要害怕又退回去,使用更小的更簡單的步驟去完成同樣的事情.。

 

 

4. 不要同時清理代碼和修正代碼

 

這是(3)的結果,但是仍然很重要。

 

這是一個常見的問題。你開始察看一個模組,是因為你想加入某個新功能。然後你發現這個代碼相當的糟糕,所以你開始重新組織它並且加入新的功能。

 

問題在於清理代碼和修正錯誤是完全不同的目標。當你清理的勢後,你想讓代碼看起來更好,而沒有改變它的功能。當你修正錯誤時, 你想改變功能。如果你同時清理代碼和改正錯誤,很難保證清理不會改變什麼。

 

 

先清理代碼,然後再在一個乾淨的基礎上,加入新的功能。

 

 

5. 刪除你沒有使用的功能

 

清理的時間正比於代碼的數量,複雜性和糟糕的程度。

 

如果代碼的功能你目前沒有使用,而且在可預見的將來也不會使用,那麼就刪除它,這會減少你瀏覽的代碼數,降低複雜度(刪除不必要的概念和依賴)。

 

你會清理的更快的,而且最後的結果會更簡單。

 

不要留著代碼僅僅因為“誰知道呢,你可能某一天需要它”。代碼是有代價的 – 它需要被移植,修正錯誤,被閱讀以及被理解。你有更少的代碼,就更好。就算在最不可能的情況下,你需要這箇舊代碼,你也能從程式碼程式庫中找到它。

 

6. 刪除大部分的注釋

 

爛代碼很少會有好的注釋。它們通常是這樣的:

 

// Pointless:

 

 

// Set x to 3

 

x = 3;

 

// Incomprehensible:

 

 

// Fix for CB (aug)

 

pos += vector3(0, -0.007, 0);

 

// Sowing fear and doubt:

 

 

// Really we shouldn‘t be doing this

 

t = get_latest_time();

 

// Downright lying:

 

 

// p cannot be NULL here

 

p->set_speed(0.7);

 

看看整個代碼。如果一個注釋對你來說不再有意義,也對你理解代碼沒什麼協助,那麼就刪除它。否則你只會浪費你的腦力去理解一堆對你理解代碼沒協助的注釋(強烈認同)

 

同樣的,刪除那些已經被注釋掉的代碼。如果你還需要它的時候,它還在你的代碼倉庫中。

 

甚至如果注釋是正確而且有用的,記住你還可以重構你的代碼。可能當你完成重構後,這些注釋不再正確了。這個世界上還沒有一個單元測試能夠告訴你注釋是否已經損壞了。

 

好代碼需要很少的注釋,因為代碼自己已經自說明了而且很容易理解。擁有好名字的變數,不需要注釋去解釋它們的用途。函數如果有好的輸入輸出,沒有特殊情況時是不需要說明的。簡單的寫得很好的演算法在沒有注釋的情況下也是容易理解的。而斷言記錄了條件和預測。

 

大部分情況下,最好的做法是刪除所有舊的注釋,專註於讓代碼變得乾淨和具有可讀性,然後再在需要的地方添加代碼 – 這些注釋反應新的API的用途以及你對代碼的理解。

 

 

7. 避免共用的可更改的狀態

 

共用的可更改的狀態是理解代碼的最大阻礙,因為它允許隔一段距離的行動,一段代碼可以改變另一段完全不同的代碼的行為。人們常說多線程是困難的。事實上,是由於線程共用了可更改的狀態,才導致了問題。如果你能避免它們的話,多線程並不複雜。

 

如果你的目標是寫高效能的軟體,你應該不能避免一切可更改的狀態,但是你的代碼仍然可以從減少它而獲益。為了“大部分功能完善”而努力吧,確保你確切的知道什麼狀態在什麼地方改變了,並且知道原因。

 

共用的可更改的狀態來自不同的地方:

 

● 全域變數。最經典的例子。現在每個人都知道全域變數的壞處。但是要注意(有時人們會忘記),全域變數是唯一的會造成問題的共用的可更改狀態。全域常量並不糟糕,Sprintf也不糟糕。

 

● 對象 – 裝有樂趣的大袋子。對象能夠集合很多方法,無疑可以共用很多可變的狀態(成員)。如果一個懶惰的程式員需要將一些資訊在方法之間傳遞的話,她可以建立一個 新成員,所以可以依照需要來讀它和寫它。這非常像全域變數。多麼有意思!當一個對象有越來越多的成員時,問題就越來越嚴重。

 

● 巨大的函數。你可能已經聽說它們了。這種神秘的產物棲息在最黑暗的代碼洞穴的最底層。心眼壞的程式員在陰暗的酒吧裡談論它們,他們的理智被他們遇見的代碼 摧毀了:“我不停地向下翻向下翻,我不能相信自己的眼睛。居然有12,000行。”當函數足夠長的時候,它們本地變數將和全域變數一樣糟糕。我們不可能知 道改變2000行之後的一個局部變數會有什麼效果。

 

 

 

● 引用和指標參數。引用和指標參數沒有被聲明為const被傳進函數時,可以在被調用者,調用者以及任何能被傳遞相同的指標的對象之間充當共用的可變的狀態。

 

這裡有一些避免共用的可更改的狀態的建議:

 

將較大的函數切分成較小的函數。

 

將較大的對象切分成較小的變數,將相關的成員放在一起。

 

將成員變成private。

 

將函式宣告const,返回結果,而不是可更改的狀態。

 

將函式宣告static,從參數獲得值,而不是從共用狀態那裡取值。

 

避免完全使用對象,實現純淨的功能,不要引入副作用。

 

將本地變數聲明const。

將指標和引用聲明const。

 

 

8. 避免不必要的複雜性

 

不必要的複雜性通常是過度工程化的結果 – 支援的結構(如序列化,引用計數器,虛擬介面,抽象工廠,訪問者等等)會拖慢真正有實際功能的代碼。

 

 

有時候過工程化,是因為一些項目開始的時候有一些更大的野心,多於實際完成的。更多的情況,我想是因為程式員讀了關於設計模式的書之後和瀑布模型之後的想法,他認為過工程化會形成更“堅固”和“高品質”的產品。

 

 

通常,這個笨重的,僵化的,過度複雜的模型不能適應功能需求,而這是設計師不期望的。那些功能可能之後用hack的方式來實現,成了在象牙塔最頂上的螺栓和後門,變成了神經錯亂的混合結構。

 

 

治癒過度工程化的方法就是YAGNI(you are not gonna need it)-你不需要它!只有當需要一個東西的時候才建造它。當你需要它的時候才建立更複雜的東西,而不是在你需要之前。

 

避免不必要的複雜性的一些實際的方法:

 

移除你沒有用到的東西(就像上面建議的一樣)。

 

簡化必要的概念,避免不必要的概念。

 

移除不必要的抽象,用實際的實現來替代。

 

移除不必要的虛擬化,並且簡化對象的結構。

 

如果一個設定曾經使用過,那麼就避免在用另外的配置來運行這個模組。

 

9. 就這麼多了

 

現在開始清理你的“房間”吧!

 

 

閱讀啟發

 

1、調整代碼,每次改動一小部分。不要多了。這樣能夠方便看出錯誤。

這次改動代碼,影響多少地方?改動太多,很難理解影響的面,難發現問題所在。

 

經驗是:改動一點點,然後提交測試。

我發現我自己犯錯。貪多。想一次性修改很多。覺得這個功能簡單,不用測試,然後修改其他地方。

 

改動很多地方,如果出現問題,你需要檢查很多地方來排查問題。

 

筆者告訴我們,一次修改太多的地方。結果導致出現問題,比較難找到原因。

正確的辦法是,修改一點,就發布一點。

發現上線後有問題,可以方便復原回來。

 

思考:如果是幾天后才發現以前的修改導致了bug。想回退到修改前的版本。麻煩的是,中間一段時間已經發布幾個版本了。回退,就會把以前的修改好的代碼返回嗎?這個問題值得我們思考!

 

 

2、擁有好的名字命名的變數,不需要注釋去解釋它們的用途。往往是自注釋的。

 

3,不要的代碼為什麼要刪除掉。不要保留在那裡。

 

很多程式員覺得,這段代碼以後可以用。所以只是暫時注釋掉。但是,遺留的代碼會讓讓系統變得不簡潔,不乾淨。增加被接手技術人員閱讀的次數,誤導他人。讓人迷惑。

 

如果的確需要,可以去程式碼程式庫裡面找回來。

 

總之,方向是讓代碼更清潔,更乾淨。提高代碼可維護性。

 

歸納:無效的代碼(注釋掉的)刪除掉。沒有意義的注釋,或者已經過時了的注釋也要刪掉。目的讓代碼變得更加簡潔。

 

4、清理代碼和修正錯誤錯誤碼的權衡。

 

同時清理和修正代碼是比較難的事情。先清理,後修正。

 

另外,要分階段來。如果不是核心的模組,有爛代碼就不管了。說不定某天給替換掉,不再使用。如果是影響核心效能的功能,要謹慎對待。

 

這個模組是長期折騰的。那麼花費些時間最佳化代碼是值得的,可以避免以後很多麻煩。

 

思考:有這種體會。先清理代碼,更加清晰,那麼修正錯誤會變得更加容易發現問題。如果先就在原來混亂的代碼基礎上修正錯誤,可能會導致修正造成新的問題出來,因為結構的不清晰。

 

5、需要對原來功能代碼進行重構。建立測試案例來測試出修改後造成的影響多少。如何建立測試案例?

 

清理代碼的閱讀筆記

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.