項目/代碼重構

由於最近一段時間一直在公司做項目優化,從中確實體會到代碼優化的必要性,同時,也看到許多代碼需要重構的必要性。結合網上的重構文章,精簡的記錄一下(程序員都很忙,而且大多時候只是爲了解決棘手的問題,不適合看長篇大論)關於代碼重構

大多出自該書:《重構-改善既有代碼的設計》(此作者就是最早提出微服務的大牛——Martin Fowler)

重構不僅僅是代碼整理,它還提供了一種高效且受控的代碼整理技術。

(一)重構前言

1、何謂重構
對軟件內部結構的一種調整,目的是在不改變軟件可觀察行爲的前提下,提高其可理解性,降低其修改成本。

2、爲何重構
改進軟件設計、幫助找到Bug、提高編程速度。

3、何時重構
任何情況下我都反對專門撥出時間進行重構。重構本來就不是一件應該特別撥出時間做的事情,重構應該隨時隨地的進行。

當然,如果代碼非常混亂,重構的代價太大,不如直接重新寫!

(二)哪些代碼需要重構——壞代碼

新版增加:神祕命令(Myterious Name)、全局數據(Global Data)、循環語句(Loop)等。

1、重複代碼
如果多個地方看到相同的程序結構,那麼可以肯定:設法將它們合二爲一,程序會變得更好 。
兩個互爲兄弟的子類內含有相同的表達式:提煉出相同代碼,將它推入超類內;
兩個毫不相干的類中出現:將重複的代碼提煉到一個獨立的類中。

2、過長的函數/方法
很顯然,一個函數太長,易讀性和邏輯性會變差。
所以:
(1)每當感覺需要以註釋說明的時候,就需要把要說明的東西寫進一個獨立函數,並以其用途命名。
(2)確定提煉哪一段代碼的方法:尋找註釋,註釋通常能指出代碼用途和實現手法之間的語義距離
(3)條件語句和循環常常也是提煉的信號

3、過大的類
單個類不要做太多的事情,否則其內往往就會出現太多實例變量。
可以將幾個變量一起提煉至新類內。提煉時應該選擇類內彼此相關的變量,將它們放在一起。通常如果類內的數個變量有着相同的前綴或字尾,這就意味有機會把它們提煉到某個組件內。

4、過長參數列
太長的參數列難以管理,太多的參數會造成前後不一致、不容易使用,而且一旦你需要更多數據,就不得不修改它。如果將對象傳遞給函數,大多數修改都將沒有必要。

5、發散式變化
所謂發散式變化是指一個類因爲多個因素的變化要改來改去,對於這種類就需要將這個類拆分開。拆分成一個類只受一個因素的影響(最好是一個因素)。

6、散(霾)彈式修改
簡單的說就是,一種變化引發多個類相應修改的情況。這種情況主要是非常容易少改相應的類。
這種情況可以把所有需要的代碼放進同一個類。如果眼下沒有合適的類可以安置這些代碼,就創造一個。

7、依戀情結
對象編程的思想是:將數據和對數據的操作行爲包裝在一起。有時候一個函數往往會用到幾個類的功能,那麼它究竟該被置於何處呢?處理原則通常爲:判斷哪個類擁有最多被此函數使用的數據,然後就把這個函數和那些數據擺在一起。

8、數據泥團
如果在很多地方看到相同的三四項數據一起出現。那麼這些總是綁在一起出現的數據應該擁有屬於他們自己的對象。
找到這些數據以字段形式出現的地方,將它們提煉到一個獨立的對象中。這麼做的直接好處是可以將很多參數列縮短簡化函數調用。

9、基本類型偏執
這個翻譯其實並不準確,只是字面翻譯而已,原文用的是:Primitive Obsession,不用太糾結這個說法。
這個情況主要是由於剛開始創建類時,字段比較少,而隨着特性的不斷增加,基本數據類型的字段就會越來越多。將它們有組織地結合起來,可以更方便的管理這些數據。
(1)如果你有大量的基本數據類型字段,就有可能將其中部分存在邏輯聯繫的字段組織起來,形成一個類。更進一步的是,將與這些數據有關聯的方法也一併移入類中。爲了實現這個目標,可以嘗試以類取代類型碼(Replace Type Code with Class) 。——提取關聯的基本屬性爲單獨的類
(2)如果基本數據類型字段的值是用於方法的參數,可以使用引入參數對象(Introduce Parameter Object) 或保持對象完整(Preserve Whole Object) 。——傳參數時,儘量使用對象作爲參數
(3)如果想要替換的數據值是類型碼,而它並不影響行爲,則可以運用以類取代類型碼(Replace Type Code with Class) 將它替換掉。如果你有與類型碼相關的條件表達式,可運用以子類取代類型碼(Replace Type Code with Subclass) 或以狀態/策略模式取代類型碼(Replace Type Code with State/Strategy) 加以處理。——通過多種方式用對象替換可以替換的代碼
(4)如果你發現自己正從數組中挑選數據,可運用以對象取代數組(Replace Array with Object) 。——從數組中過濾數據時,就用對象代替數組

10、switch驚悚現身——不要出現switch——重複的Switch
簡單的說就是:少用switch語句,而應該考慮以多態來替換它。從本質上說,switch語句的問題在於重複。
但是,如果只是在單一函數中有些選擇實例,且並不想改動它們或者說固定不變的情況,那麼多態就有點殺雞用牛刀了,沒必要。

11、平行集成體系
存在這麼一種情況:每當你爲某個類增加一個子類,必須也爲另一個類相應增加一個子類。(其實,平行繼承體系就是散彈式修改的特殊情況)
那麼,消除這種重複性的一般策略是:讓一個繼承體系的實例引用另一個繼承體系的實例。

12、冗餘類——項目中不要出現多餘的類
某個類中的功能沒有用的話,就及時刪除掉或者將有用的極少部分代碼合併到父類中,並刪掉該類。

13、誇誇其談未來性——過度設計
如果某個抽象類其實沒有太大作用,可以將超類和子類合爲一體。將不必要的委託轉移到另一個類中,並消除原先的類。如果函數的某些參數未被用上,那麼就將參數移走。如果函數名稱帶有多餘的抽象意味,就應該對它重命名,讓它現實一些。

14、令人迷惑的暫時字段——臨時字段
某個實例變量僅爲某種特定的情況而設。這樣的代碼在一般情況下,讓人不容易理解,看不出該變量的用途。
可以將這種實例變量單獨創建一個類,並註明作用。

15、過度耦合消息鏈 —— 過長的消息鏈
如果請求一個對象時,這個對象需要請求另一個對象,然後再請求別的對象………這就是消息鏈。採用這種方式,意味着客戶代碼將與查找過程中的導航結構緊密耦合。一旦對象間的關係發生任何變化,客戶端就不得不做出相應的修改。
這時候我們可以隱藏“委託關係”,並在服務類上建立客戶所需要的所有函數。你可以在消息鏈的不同位置進行這種重構手法。理論上是可以重構消息鏈上的任何一個對象,但是這樣做往往會把一系列對象都變成“中間人”。通常更好的選擇是:先觀察消息鏈最終得到的對象是用來幹什麼的,再看看能否通過抽取方法把使用該對象的代碼提煉到一個獨立函數中,然後再將這個函數推入消息鏈。

16、中間人
對象的基本特徵之一就是封裝——對外部世界隱藏其內部細節。封裝往往伴隨着委託。你也許會看到某個類接口有一半的函數都委託給其他類,這樣就是過度運用。
這時候就應該移除中間人,直接和真正的負責人打交道。如果這樣“不幹實事”的函數只有少數幾個,可以將它們放進調用端。如果中間人還有其它行爲,可以把它變成實責對象的子類,這樣你既可以擴展原對象的行爲,又不必負擔那麼多的委託動作。

17、狎暱關係 —— 內幕交易(Insider Trading)
類與類之間過分緊密的關係必須拆散——可以引入第三方類或者利用委託。

18、異曲同工的類
兩個函數做同一件事,卻有着不同的簽名。但這往往不夠,可以反覆將某些行爲移入類中,直到兩者的協議一致爲止。
當然,如果你必須移動大量代碼纔可以完成這個工作,那還不如直接構建一個父類。

19、不完美的庫類 —— 最新版中去掉了這個
很多第三方庫提供的接口經常不能恰如其分得滿足我們的需求,這時候就需要對第三方接口做一層轉換,或者給它添加一定的行爲。

20、純稚的數據類——這個壞代碼被翻譯的確實很業餘
Data Class,爲啥說是純稚呢?它們擁有一些字段,以及用於訪問(讀寫)這些字段的函數,除此之外一無長物。這樣的類只是一種不會說話的數據容器,它們幾乎一定被其它類過分細瑣地操控着。Data Class就像小孩子,作爲一個起點很好,但若要讓它們像成熟的對象那樣參與整個系統的工作,它們就必須承擔一定責任。所以叫純稚
這些類早期可能擁有public字段,果真如此就應該在別人注意到它們之前將它們封裝起來。如果這些類內含容器類的字段,就應該檢查它們是不是得到了恰當的封裝;如果沒有,就把它們封裝起來。對於那些不該被其它類修改的字段,就應該去掉該字段的所有設值函數。

21、被拒絕的遺贈——Refused Request,這個翻譯也很業餘
一般來講,子類應該繼承超類的一切,但如果它們不想或者不需要繼承,又該怎麼辦呢?按照傳統說法,這就意味着繼承體系的設計錯誤。你需要爲這個子類新建一個兄弟類,然後讓父類只包括兩個子類共享的部分。
一般而言,這就足夠了,但是如果子類不願意支持超類提供的接口,則說明不能使用繼承處理,應該使用委託。

22、過多的註釋
註釋當然是必須的,但是不要因爲代碼的混亂而加註釋,遇到這種情況時,請先重構代碼後再加必要的註釋。

在實際項目開發中,我們不可能一條一條的比對是否會犯以上提到的問題,以上的這些問題還是需要根據實際情況而定。當然,並不是說以上這些問題不重要,而是基於這些經常出現的問題,培養我們良好的編碼習慣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章