設計模式之再讀重構


      這篇博客本來是幫朋友的教學網站寫的系列課程,但是因為格式、案例等原因要讓我重新修改,我這個人最煩的就是這些條條框框。所以一氣之下就沒有發出去,索性就直接寫在自己的博客里,總感覺還是這樣來的舒服、隨意。

      重構(名詞上的定義):對軟件內部結構的一種調整,目的是在不改變軟件可觀察行為的前提下,提高其可理解性,降低其修改成本。重構(動詞上的定義):使用一系列重構手法,在不改變軟件可觀察行為的前提下,調整其結構。在項目開發過程中,兩頂帽子的故事:如果我們把項目開發過程中的添加新功能、以及重構想象成兩頂帽子的話。那么在添加新功能時,你不應該修改既有代碼,只管添加新功能。通過測試,你可以衡量自己的工作進度。重構時你就不能再添加功能,只管改進程序結構,此時你不應該添加任何測試,只在絕對必要時才修改測試。但是這兩頂帽子,我們在實際的項目開發過程中,往往需要來回的替換,具體可看自己的項目進度衡量。

      在軟件設計里面有個著名的破窗理論,大意為:在一座新房子里面,一旦開始出現一扇破窗,那么這扇破窗的下面將很快出現垃圾,同時房子的其他部分都將陸續出現破敗的情況。將這個破窗理論借用到我們軟件開發中,我們將會看到一個原來設計很好的架構系統,如果不注意維護的話,那么將很快出現第一個代碼上的破窗。這個時候,如果我們不注意及時重構修補的話,那么破窗效應將蔓延,最終整個項目有可能面臨失控的危險。所以重構帶來的第一個好處,將是改進軟件設計。

      常常會有人有疑問,在項目里面是不是應該撥出專門的時間用於代碼重構呢?答案是否定的,重構應該是隨時隨地進行,你不應該為重構而重構,因為你想做別的什么事時,重構可以幫助你把那些事情做好。Don Roberts給了我們一條准則:第一次做某件事時只管去做;第二次做類似的事會產生反感,但是無論如何還是可以去做;第三次再做類似的事,你就應該重構。

      在極限編程里有這么一種觀點:重構可以取代預先設計,就是說你根本不必做任何設計,只管按照最初想法開始編碼,讓代碼有效運作,然后再將它重構成型。其實,我個人還是不同意這個觀點的。因為一個系統的開始之初,肯定會伴隨着一個設計思路的誕生。設計思路的誕生,必然會讓我們聯系自己的一些過往經驗,評估出一種高可行、高穩定的方案出來。但是在這個過程中,不管多牛的人,都不可能設計出100%完美的系統。所以這個時候重構就將作為一種重要的手段,牽着系統走向完美、穩定之路。下面的內容將具體講解一下,重構的典型案例,以及如果重構。

      相比大家都聽說過代碼的壞味道吧,Martin Fowler在一次去蘇黎世拜訪Kent Beck交流重構這個微妙問題時候,因為受到剛出生的女兒的氣味影響,提出來用味道形容代碼。他們將一些寫的不好的代碼,稱作該代碼有壞味道。但是我們不需要提出什么精確的標准來衡量代碼壞味道,因為當我們有一定代碼量之后,我們自己就將發現重構有的時候憑的就是一種直覺。當我們看到一段很長的代碼之后,就會想要將其變小、變簡單。當我們看到一個方法的名字里面包含1、2、3這樣的隨意命令時,就會想要將其改掉。這就是一種重構的嗅覺,當我們有了很靈敏的嗅覺之后,就能夠很容易的聞出代碼的壞味道。

      在代碼的眾多壞味道里面,首當其沖的就應該是重復代碼。為什么說重復代碼是最壞的味道呢?下面筆者就講一個自己的親身經歷:一個涉及到金額支付的App項目里面,包括兩個頁面“首頁面”和“支付頁面”。在首頁面的時候,它會展示所有的支付方式里面金額最低的那一個(因為針對不同的支付方式,會有不同的優惠活動);然而點擊到支付頁面的時候,它將詳細的展示所有的支付方式以及價格。雖然,以上兩個頁面展示的形式不同,但是金額的計算方法卻是一樣的。早先也許是忙於趕進度吧,兩個頁面各自計算,這樣就導致了一次嚴重的上線事故。因為在一次計算公式變更的時候,開發人員只修改了其一,而未改其二,導致了兩處的價格不一致。

      然后第二種經常發生的壞味道就是過長的函數,擁有短函數的對象會活得比較好、比較長。“間接層”所能帶來的全部利益---解釋能力、共享能力、選擇能力---都是由小型函數支持的。這個時候,有些人可能就會擔心拆分過多的小函數會不會帶來一定程度的性能損失呢?也就是說小函數越多,調用次數就越多,程序就運行的越慢。其實完全不需要擔心這個問題,現代OO語言幾乎已經完全免除了進程內的函數調用開銷。最終的效果是:你應該更積極地分解函數。我們遵循這樣一條原則:每當感覺需要以注釋來說明點什么的時候,我們就把需要說明的東西寫進一個獨立函數中,並以其用途(而非實現手法)命名。我們可以對一組甚至短短一行代碼做這件事。哪怕替換之后的函數調用動作比函數自身還長,只要函數名稱能夠解釋其用途,我們也該毫不猶豫地那么做。如果函數內有大量的參數和臨時變量,它們會對你的函數提煉形成阻礙。這個時候我們就可以考慮提取整個對象。如何確定該提煉哪一段代碼呢?一個很好的技巧是:尋找注釋。它們通常能指出代碼用途和實現手法之間的語義距離。條件表達式和循環常常也是提煉的信號。

      一直以來代碼界就存在一個比較有意思的話題,到底代碼里面應不應該加注釋呢?從嗅覺上說,注釋不是一種壞味道,事實上它們還是一種香味呢。但是過猶不及,有些人過度使用,把注釋當做一種除臭劑來使用。常常會有這樣的情況:你看到一段代碼有着長長的注釋,然后發現,這段注釋之所以存在是因為這段代碼寫的太糟糕。其實本質上來說,注釋可以幫助我們更好的理解代碼,找到代碼里面壞味道。但是有的時候,如果我們對某段代碼進行了修改,這個時候原來的注釋可能就會變得多余或者已經具有迷惑性的不准確了。所以基於上面的分析,筆者自己持有的觀點就是盡量少些注釋,因為如果一段代碼你需要添加注釋去說明問題的話,那么就已經說明這段代碼自解性太弱了。這個時候,我們就應該想辦法重新調整變量或者函數的命名。

      其實在那篇系列教程里面,還講解了很多優化重構的方法、技巧。在這里筆者就不想多說了,但是有一點多態替換switch、if等條件判斷語句,在我們平時的項目里面肯定會經常用到,具體的做法就是將每一條判斷條件拆分為一個對象,當需要新的判斷條件時只需要新建對象即可。這樣就是面向擴展開放、面向修改關閉了。

      如果有什么寫的不對的地方,歡迎拍磚,see you!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM