重構改善既有代碼的設計 – 原則篇
重構大到模式設計,也小到一個接口函數
何時重構
三次法則
- 第一次做某件事只管去做,第二次做類似的事情會產生反感,但還是可以做,第三次再做類似的事情就應該重構
添加功能時重構
- 代碼的設計無法幫助我輕鬆的添加需要的功能特性。
修補錯誤時重構
- 調試代碼修改Bug的過程中發現代碼可讀性不好,可以用重構來加深理解
壞代碼的味道
- 重複代碼(Duplicated Code)
- 在一個以上的地方發現相同的程序結構,可以肯定的重構,設法合而爲一。
- 過長函數(Long Method)
- 如果函數內有大量的參數和臨時變量,阻礙了代碼閱讀。總之一句話
程序越長,越難理解
當你感覺需要以註釋來說明點什麼的時候,就需要把需要說明的東西寫進一個獨立的函數
- 函數名稱命名要能夠體現函數# 重構改善既有代碼的設計
重構大到模式設計,也小到一個接口函數
何時重構
三次法則
- 第一次做某件事只管去做,第二次做類似的事情會產生反感,但還是可以做,第三次再做類似的事情就應該重構
添加功能時重構
- 代碼的設計無法幫助我輕鬆的添加需要的功能特性。
修補錯誤時重構
- 調試代碼修改Bug的過程中發現代碼可讀性不好,可以用重構來加深理解
壞代碼的味道
- 重複代碼(Duplicated Code)
- 在一個以上的地方發現相同的程序結構,可以肯定的重構,設法合而爲一。
- 過長函數(Long Method)
- 如果函數內有大量的參數和臨時變量,阻礙了代碼閱讀。總之一句話**
程序越長,越難理解
** 當你感覺需要以註釋來說明點什麼的時候,就需要把需要說明的東西寫進一個獨立的函數
- 函數名稱命名要能夠體現函數 “做什麼” 而不是 “怎麼做”
- 過大的類 (Large)
- 單個類想要做太多的事情,內部出現太多的實例變量,一方面會有重複代碼的問題,另一方面類會很混亂。
- 舉個Ext的例子,Ext的Form元素,都有基類Field只提供基礎函數getJsonValue和setJsonValue,子類TextField、TextArea、ComboBox都是基於這個組件來寫的,假設Field組件想要把TextField和Combox組件的功能都實現,代碼會顯得很冗餘
- 過長參數列 (Long Parameter List)
- 太長的參數列難以理解,太多參數會造成前後不一致,不易使用,而且一旦你需要更多數據,就不得不修改它。可以把對象傳遞給函數,大多數修改都沒有必要,因爲你可能只需要在函數內增加一兩條請求,就能得到更多數據。
- 發散式變化(Divergent Change)
- 如果某個類經常因爲不同的原因在不同的方向上發生變化。比如在AF的數據表格展示身上,增加一種類型,不止一處需要對應修改,這個很容易造成改動引發。針對某一外界變化的所有相應修改,都只應該發生在單一類中,而這個新類內的所有內容都應該反映此變化。
- 霰彈式修改(Shotgun Surgery)
- 如果每遇到某種變化,你都必須在許多不同的類內做出許多小修改,你所面臨的壞味道就是Shotgun Surgery。如果要修改的代碼散落各處,你不但很難找到它們,也很容易遺忘某個重要的修改。
Divergent Change 是指一個類受多種變化的影響,而Shotgun Surgery則是指一種變化引發多個類做出相應修改。這兩種情況下你都會希望整理代碼,使外界變化與需要修改的類趨於一一對應。
- 依戀情結(Feature Envy)
- 函數對某個類的興趣高過對自己所處類的興趣。這種孺慕之情最通常的焦點便是數據。場景:某個函數爲了計算某個值,從另外一個對象身上調用的幾乎半打的取值函數。所以可以把它移到它該去的地方。
- 一個函數往往會用到幾個類的功能,那麼它應該被置於何處呢?**
原則是:判斷哪個類擁有最多被此函數使用的數據,然後就把這個函數和那些數據擺在一起。最根本的原則是:將總是一起變化的東西放在一塊兒。數據和引用這些數據的行爲總是一起變化的。
- 數據泥團(Data Clumps)
- 可以在很多地方看到相同的三四項數據:兩個類中相同的字段、許多函數簽名中相同的參數。這些總是綁在一起的數據真應該擁有屬於它們自己的對象。可以將它們提煉到一個獨立對象中
- 基本類型偏執(Primitive Obsession)
- 大多數編程環境都有兩種數據:結構類型允許你將數據組織成有意義的形式:基本類型則是構成結構類型的積木塊。結構總是會帶來一定的開銷。對象的一個極大的價值在於:它們模糊(甚至打破)了橫亙於基本數據和體積較大的類之間的界限。就比如喜歡傳遞很多參數,而不使用一個對象傳參的形式
- Switch驚悚現身(Switch Statements)
- 面向對象程序的一個最明顯特徵就是:少用switch或case語句。從本質上說,switch語句的問題在於重複。你常會發現同樣的switch語句散步於不同地方。如果要爲它添加一個新的case子句,就必須找到所有switch語句並修改它們。switch語句常常根據類型碼進行選擇,你要的是“與該類型碼”相關的函數或類。所以應該將switch語句提煉到一個獨立函數中。
- 平行繼承體系(Parallel InheritanceHierarchies)
- 當你爲某個類增加一個子類,必須也爲另一個類相應的增加一個子類。如果你發現某個繼承體系的類名稱前綴和另一個繼承體系的類名稱前綴完全相同,便是聞到了這種壞味道。消除這種重複性一般策略是:讓一個繼承體系的實例引用另一個繼承體系的實力。
- 冗餘類(Lazy Class)
- 如果一個類不值得其身價,它就應該消失。因爲你所創建的每一個類,都得有人去理解它、維護它,這些工作都是要花錢的。比如一個組件裏面需要獲取一個Person的address、tel、name的時候,寫了三個get函數,而不是直接返回Person的時候
- 誇誇其談未來性(Speculative Generality)
- 如果某個抽象類沒有太大的作用,可以直接去掉,再或者有些參數目前沒用到,可以移除。
- 令人迷惑的暫時字段(Temporary Field)
- 有一種對象是這樣的:其內某個實例變量僅爲某種特定情況而設。這樣的代碼會讓人很不理解,通常認爲對象所在的字段都需要他所有的變量。比如某個算法,由於不希望傳遞多個參數,而使用一個對象的時候,但是這些字段只在使用算法的時候才需要使用,這個時候應該把這些特殊字段,抽離成一個獨立的函數。
- 過度耦合的消息鏈(Message Chains)
- 如果你看到用戶向一個對象請求另一個對象,然後再向後者請求另一個對象,然後再請求另一個對象,這就是消息鏈。實際代碼中,你可能看到的是一長串的getThing()或一長串臨時變量。採取這種方法意味着客戶代碼將與查找過程中的導航結構緊密耦合。一旦對象間的關係發生任何變化,客戶端將不得不做出相應修改。這個場景在AF裏面還挺多,跨組件調用函數,getFormWin().getJsonForm().getField(‘xxxx’)
- 中間人(Middle Man)
- 對象的基本特徵之一就是封裝----對外部世界隱藏其內部細節。封裝往往伴隨委託。比如你問主管是否有時間參加一個會議,他就把這個消息“委託”給他的記事簿,然後才能回答你。但是人們可能過度使用委託,你也許會看到某個類接口有一半的函數都委託給其他類,這樣就是過度運用。
- 狎暱關係(Inappropriate Intimacy)
- 兩個類過於密切,花費太多時間去探究彼此的private成分。這是不好的行爲,我們希望類嚴守輕軌。這個規範在checklist中有一條叫解耦來着, AF的ACL應用控制策略模塊,有類似情況,表單彈窗的提交邏輯寫在了Mgr中,導致後續表單複用的時候,需要實例化這個Mgr傳入,組件之間沒有解耦開來。
- 異曲同工的類(Alternative Classes with Different Interfaces)
- 如果兩個函數做同一件事,卻有着不同的名稱,請根據據他們的用途重新命名,但這往往不。還可能需要反覆的抽離公共函數出來。
- 不完整的庫類 (Incomplete Libray Class)
- 舉個例子,表格勾選組件,原來不支持單選模式和多選模式的切換,這個時候這個組件庫又不能改的情況,可以繼承勾選組件,在原來勾選的基礎上再寫一個組件,而不是去改原來的組件庫的代碼。
- 純稚的數據類(Data Class)
- 所謂Data Class是指:它們擁有一些字段,以及用於訪問(讀寫)這些字段的函數,除此之外一無長物。這樣的類只是一種不會說話的數據容器,它們幾乎一定被其他類過分細瑣的操縱着。 沒想到有啥壞處
- 過多的註釋(Comments)
- Comments不是一種壞味道,事實上,它們還是一種香味呢。常常會看到這樣的情況:你看到一段代碼有着長長的註釋,然後發現,這些註釋之所以存在乃是因爲代碼很糟糕。這種情況的發生次數之多,實在令人吃驚。
當你感覺需要撰寫註釋時,請先嚐試重構,試着讓所有註釋都變得多餘。
代碼的味道之解決方法
代碼味道 | 常用方式 |
---|---|
異曲同工的類 | 函數重命名,移動函數 |
過多的註釋 | 函數抽離 |
純稚的數據類 | 移動函數、封裝字段或者集合 |
數據泥團 | 類抽離、引入對象參數、保持對象完整 |
發散式變化 | 函數抽離 |
重複代碼 | 函數抽離、塑造模板函數、函數上移 |
依戀情節 | 移動函數、函數抽離 |
狎暱關係 | 移動函數、函數抽離、用委託替代繼承、隱藏“委託關係” |
不完整的庫類 | 引入外加函數、引入本地拓展、接口抽離 |
過大的類 | 抽離子類、用對象代替數據值 |
冗贅類 | 將類內聯化、摺疊繼承關係 |
過長函數 | 函數抽離、用查詢代替中間變量、分解條件表達式、用函數代替對象 |
過長參數列 | 用函數代替對象、引入對象參數、保持對象完整 |
過渡耦合消息鏈 | 隱藏“委託關係” |
中間人 | 移除中間人、以繼承代替委託 |
平行繼承關係 | 移動函數 |
基本類型偏執 | 用對象代替數據值、引入參數對象、用子類代替類型碼 |
被拒絕的遺贈 | 用繼承代替委託 |
霰彈式修改 | 移動函數、將類內聯化 |
誇誇其談未來性 | 摺疊繼承關係、移除參數、函數重命名 |
switch驚悚現身 | 以多態取代條件表單式、用類取代類型碼、以明確函數取代參數 |
令人迷惑的字段 | 抽離類 |