1. 程式人生 > >重構:幹掉有壞味道的程式碼

重構:幹掉有壞味道的程式碼

第一次讀[重構 - 改善既有程式碼的設計(Refactoring: Improving the Design of Existing Code)](https://book.douban.com/subject/1229923/)這本書還是在學校的時候,那個時候剛開始寫Java程式碼,師兄推薦了兩本書《重構》、《設計模式》。在今日看來,這兩本書都是經典好書,得謝謝我的師兄。 最近,打算重新讀一下這兩本書,先讀了重構,感覺還是收穫頗多。想來這兩本書都是比較偏向實踐的,並不是讀一遍就可以束之高閣了,而是應該常讀常新。 本文地址:[https://www.cnblogs.com/xybaby/p/12894470.html](https://www.cnblogs.com/xybaby/p/12894470.html) --- --- 去年重讀了[程式碼整潔之道](https://book.douban.com/subject/4199741/)這本書,也寫了一篇筆記 [什麼是整潔的程式碼](https://www.cnblogs.com/xybaby/p/11335829.html)。今年重讀《重構》的時候,發現書中很多內容都是相同的,作者好像都是叫 Martin 什麼的,我還想難道是同一個人? 查了一下,並不是,重構的作者是 [Martin Fowler](https://en.wikipedia.org/wiki/Martin_Fowler_(software_engineer));而clean code的作者是 [Robert C. Martin](https://en.wikipedia.org/wiki/Robert_C._Martin) ,江湖人稱 "Uncle Bob"。 ![](https://img2020.cnblogs.com/blog/1089769/202005/1089769-20200515134231967-200363269.png) 不過好像兩位都在面向物件、敏捷領域有所建樹。By the way,重構的第一版寫於1999年,(本文也基於第一版的譯文),而clean code的第一版寫於2009年,且clean code是有參考 “refactoring: Improving the Design of Existing Code”的。 在我看來,重構這本書的核心價值有三部分: - 指出有“壞味道”的程式碼 - 對這種程式碼給出重構的詳細步驟,這些步驟保證重構過程是安全的 - 關於引入新技術、新思想的一些思考,如重構、程式碼複用、TDD 當然,第二部 -- 針對各種有問題的程式碼的重構步驟 -- 是本書的重點,不過現在的IDE都提供了對重構的支援,大大提升了重構的效率和安全性。 # 認清重構的事實 書名叫 Refactoring: Improving the Design of Existing Code ,作者也著重強調:**重構是在不改變軟體可觀察行為的前提下改善其內部結構**。也就是說,對外的API,以及API的行為不應該被改變,不要在重構的同時修bug,或者新增新功能。 重構是為了改善程式的內部結構,而改善的目的在於增加程式碼的可讀性,讓程式碼更容易維護和修改。 我們也常常為了提升效能而修改程式碼,不幸的是,為了效能而實施的修改通常讓程式碼變得難以維護,標誌就是得加註釋說明為什麼要這麼修改。 **重構的前提** 不管怎麼樣,手動還是藉助工具,重構還是會修改程式碼,只要修改程式碼,就可能引入錯誤。那麼重構給出了就是一套經過驗證的、有條不紊整理程式碼的方法,通過**逐步改進、及時測試、出錯則回滾**的方法來最小化引入bug的概率。 上面提到了逐步驗證,這就需要在重構的時候需要有可靠的、自動的測試環境,如果沒有靠譜的測試方案,那麼最好還是不要重構。 **什麼時候重構** 程式設計師新學得一個技能, 比如重構,就很容易認為這是解決程式設計問題的屠龍技,迫不及待想找個環境用起來,但只有在合適的時機使用才能發揮其效用。 - 增加新的功能前 - 修改bug前 - code review時 成功的軟體都需要長時間的維護、迭代,那麼我們程式設計師難免就會接受其他程式設計師的遺產:程式碼以及bug。如果需要在舊程式碼上加新功能,但舊程式碼的混亂程度又讓人無從下手,該怎麼辦呢? - 重寫:既然之前的程式碼很SB,那我就重新寫點NB的程式碼。但現實很殘酷,重寫的時間、人力成本是多少?引入的新BUG怎麼算?況且,如果貿然動手,新造的輪子還可能不如原來的輪子。 - 複製、修改:看看系統中有沒有類似的功能模組,複製過來,改一改,如果恰好能工作,那就萬事大吉。但我們知道,重複的程式碼是不好的,bug、“有壞味道”的程式碼也被複制和傳播。 - 重構:處於重寫與複製的中間狀態,在不修改程式的外在表現的情況下,改善程式碼的質量,也讓新功能的新增更加容易。這也符合clean code中提到的童子軍軍規: > 讓程式碼越來越好,而不是越來越壞 **重構與設計** 不管是瀑布流模型開發,還是敏捷開發,都是需要有設計的。過度設計和不做設計都是有問題的,而重構簡化了設計:無需過度追求靈活些,合理即可。所謂靈活些,即可應對各種需求變化,但靈活的系統比簡單的系統複雜得多,且難以維護。 重構使得修改面向物件的程式設計變的很容易,因為可以重構繼承體系,將field、method移動到不同的類中,通過多型移除各種複雜的條件判斷。某種程度上,重構可以簡化詳細設計,但不能替代架構設計,或者說概要設計。 值得注意的是: - 本書的重構手法只適合單程序單執行緒程式,而不一定適合多執行緒、分散式。對於多執行緒,一個簡單的inline就可能導致各種問題。而對於分散式系統的重構,更多的是架構層面的設計。 - 越難重構的地方,越需要精心設計,比如資料庫欄位,通訊協議,對外介面。保持對舊協議的相容是一件非常麻煩的事情。 # 有“壞味道”的程式碼 需要重構的程式碼往往都散發著“壞味道”,讓專業的程式設計師感受到不舒服。這一部分,羅列了作者總結的“壞味道”。 需要注意的是,本書羅列的壞味道不一定很全面,比如一個變數命名為```temp```,大概率就是一個壞味道,但本書中就未提及這種情況。因此,**非常建議配合[clean code](https://www.cnblogs.com/xybaby/p/11335829.html)一起閱讀**。 另外,個人覺得本節還有一個嚴重問題:那就是缺乏例子。“壞味道”是我們為什麼要重構,而後面的具體手法是如何重構,why 比 how 更重要些,所以個人感覺應該在描述"壞味道"的時候給出程式碼示例。 **重複的程式碼 -- duplicated code** 最簡單的情況,就是兩段程式碼有相同的表示式語句,處理方法也很明確,那就是```extract method```,然後應用這個新的方法。另外一種常見情況,就是這兩段相同的程式碼位於不同的子類 -- 往往是新增子類的時候部分複製了其他子類的程式碼,這個時候就應該使用```pull up method```將公共程式碼抽取到基類去。 當然,兩段程式碼也可能是相似但不完全相同,那麼可以考慮將差異部分子類化,即使用```form template method```。或者將差異部分引數化,即通過引數控制不同的邏輯,但需要注意的是,引數會不會導致兩種截然不同的行為,即```parameterize method```與```replace parameter with explicit methods```的區別。 最後,也經常發現兩個類之間有相同的重複程式碼,但是二者之間並沒有繼承關係(並不是is-a關係),那麼可以```extract class```將公共部分提取出來,以組合的方式使用,或者使用多繼承--[Mixin](https://www.cnblogs.com/xybaby/p/6484262.html) 繼承其實現。 **過長的函式 -- long method** 過長的函式往往冗雜著過多的細節,在[什麼是整潔的程式碼](https://www.cnblogs.com/xybaby/p/11335829.html)一文就曾經中, 程式碼的組織應該像金字塔一樣,“每個函式一個抽象層次,函式中的語句都要在同一個抽象層級,不同的抽象層級不能放在一起”。 對於過長的函式,負責任的程式碼作者往往會給出一些註釋:解釋某一小段程式碼的作用,這其實就暗示著我們可以把這段程式碼移到一個獨立的函式,然後取一個恰當的名字來展現其意圖。這個新函式的名字應該體現做什麼,而不是怎麼做,這樣,新函式的名字就可以取代原來的註釋。 如果新抽取出來的子函式需要用到原函式中的引數或者臨時變數,那麼這些都需要引數化到子函式,這可能導致子函式引數列表過長的問題,這個問題及其解決辦法在後面闡述。 除了註釋,還有什麼“味道”暗示應該提取子函式呢,比如 ```if then else```中有大段的程式碼,這個時候可以使用```Decompose conditional```處理條件表示式。 **過大類 -- large class** 單個類有太多的例項屬性,而且其中某些屬性經常獨立於其他屬性一起使用,那麼可以使用```extract class```。 比如一個課程資訊類 ```Course```,裡面包含了 ```CourseId、CourseName、TeacherId、TeacherName、TeacherSex``` 等屬性,那麼壞味道就是:很多屬性名擁有相同的字首。因此可以通過```extrace class```將 ```CTeacherId、TeacherName、TeacherSex``` 抽取到新的類 ```Teacher```。然後就可以去掉這些屬性名的字首,同時```Course```類持有 ```Teacher```即可。 或者一些屬性只在某些特殊狀態下使用,那麼可以考慮```extrace subclass```。 **過長引數列表 -- long parameter list** 過長的引數列表讓程式碼變得難以閱讀和理解,要搞清楚每個引數的意義就需要大費周折。 如果某個引數可以從函式內可訪問的物件(類屬性或者其他引數)獲得,那麼這個引數就是冗餘的,就可以 ```replace parameter with method```。 另外,傳遞的若干個引數可能只是某個物件的一堆屬性,那麼就可以考慮直接傳遞該物件 ```preserve whole object```,不過需要注意,```preserve whole object```可能會導致非預期的依賴關係,這在靜態型別語言(如C++)中又是一個複雜問題。 **發散式變化 -- Divergent change** 某個類由於不同的原因要在不同的地方進行修改,事實上,這違背了類的單一職責原則(SRP),通常也是過大類。解決的辦法就是拆分成不同的類(子類)。```extract class``` or ```extract subclass``` **散彈式修改 -- shotgun surgery** 與 ```Divergent change``` 恰好相反,為了需要響應一個變化而修改大量的類 **依戀情結 -- feature envy** 函式對某個類的興趣高於自己所在的類。如大量使用其它類的資料,常見的是取出其他物件的屬性,然後一通計算後再賦值。解決辦法,將總是一塊兒變化的東西放在一起:資料與對資料的操作。 **資料泥團 -- Data clumps** 如果某些資料經常一起變化,那麼應該將這些資料提取到某個類中,正如之前過大類中的例子。提取出單獨的類,減少了屬性和引數的個數,而且接下來就可以找出 ```feature envy```,進一步重構。 **基本型別偏執 -- primitive obsession** 類似於上一條“資料泥團”,不過更強調基本資料的封裝 使用基本型別,比如用兩個欄位 ```begin```, ```end``` 來表示區域```[begin, end)```,僅從可讀性上來說肯定不如封裝成一個類 ```range``` **switch** switch 的問題在於重複,這裡需要switch case,那麼很可能其他地方也要switch case。如果增加一種case,那就得到處修改,違背OCP原則。 使用多型是常用的解決辦法,```replace condition with polymorphrsim```,過程是這樣子的: 1. ```extract_method``` 1. ```move method``` 1. ```replace type code with subclass(strategy、state)``` 1. ```replace condition with polymorphrsim``` **平行繼承體系 -- parallel inheritance hiearachies** 這是```shotgun surgery```的一種特化,某各類增加了一個子類導致另外一個類也必須增加一個子類,雖然設計模式中可能出現這樣的情況,但壞味道可以幫助我們加以區分:某個繼承體系的類名字首和另一個繼承體系的類名字首完全相同 **冗餘類 -- Lazy class** 沒有什麼價值的類。類中不在有什麼實質性工作,可能是因為邏輯變化,可能是因為重構,這個時候可用通過```collapse hierarchy``` 或者 ```inline class```去掉這樣的類。 **誇誇其談未來 -- speculative generality** 過度的設計、抽象、泛化,各式各樣的鉤子和特殊情況處理,越靈活越複雜,越是難以維護。壞味道:函式或類的唯一使用者是測試用例 **令人迷惑的暫時欄位 -- Temporary Field** 某個成員變數只是在某些特殊情況才會用到,不用到的時候會導致迷惑,或者某個成員變數的賦值只是為了後續方便某個成員方法的呼叫,根據不同的情況可以參考一下重構手法: - ```extract class```將這些特殊的field移到新的類 - 使用 ```null object```避免寫出條件分支 - 函式呼叫時傳入這些特殊變數 **過度耦合的訊息鏈 -- message chain** 對某一個物件不停索求另一個物件,壞味道就是 ```A.getB().getC().dosth()```,這就是 clean code 中提到的火車失事,違背了德墨忒爾律(The Law of Demeter):**模組不應瞭解他所操作的物件的內部情況** 解決的辦法是```Hide delegate```, 但這樣的重構又可能導致下一個問題:```middle man``` **中間人 -- middle man** 過分使用委託,如果一個類的多半介面都是委託給其他類,那麼可以考慮```remove middle man```。這有點類似[軟體架構模式](https://www.cnblogs.com/xybaby/p/11918485.html)中提到的汙水池反模式(architecture sinkhole anti pattern) 如果middle man也有一些職責,可以考慮 ```replace delagate with inheritance``` 讓其變成最終物件的子類。 **狎暱關係 -- inappropriate intimacy** 兩個class過於親密,使用彼此的private。抽取出新的類,或者```move filed``` **不完美的類庫 -- incomplete library class** 類庫是程式碼複用的絕佳體現,但是類庫的作者不可能預料到所有的需求,因此怎麼在不改原始碼的基礎上完成想要的工作: - ```introduce foreign method``` - ```introduce local extension``` **被拒絕的饋贈 -- Refused Bequest** 壞味道:子類複用了基類的行為(實現),但卻不想支援基類的介面,這違背了LSP原則:子型別必須能夠替換它們的基型別。 C++中public繼承的其實就是介面,而private繼承的則是實現,通過private繼承,基類中的所有方法都變成private。更通用的重構手法: ```replace inheritance with delagate``` **過多的註釋 -- comments** > 註釋是好東西,散發著香味,但你不應該用它來掩蓋臭味 使用```extract method```或者```rename method```來解釋註釋的行為。對於引數的命令也應該能望文知義 # 具體的重構手法 找到壞味道之後,就是如何安全的進行重構,書中羅列了各種重構手法的具體的實施步驟,按照這種逐步推進、逐步測試的方法,保證重構沒有影響到程式碼的外在表現。當然,IDE提供的重構工具讓部分重構變得更加容易和安全。 ## 重新組織函式 函式總是過長,尤其是在漫長的維護過程中,函式內的程式碼數量會逐漸膨脹。 **Extract method** 需要注意: - 保證函式名稱與函式本體之間的語義距離 -- 一個好的函式名 - 對於區域性變數和引數的處理:引數 **Inline Method** 難點: - 是否是多型 - 得找出所有引用點 **Inline temp** 臨時變數只是被一個簡單表示式賦值一次。有助於後續的```Extract method```,也可以作為```replace temp with query```的一部分使用。 注意: - 如果表示式較為複雜不應內聯,影響可讀性與效率 - 多次賦值的話也不能內聯 **replace temp with query** 將一個表示式提取為一個單獨的函式,新函式可以被其它函式呼叫。之中有一段例項程式碼,用python改寫如下: ``` def calc_price(self): base_price = self._quality * self._item_price if base_price > 1000: return base_price * 0.95 else: return base_price * 0.98 ``` 重構後是這樣的 ``` def calc_price(self): if self.base_price() > 1000: return self.base_price() * 0.95 else: return self.base_price() * 0.98 def base_price(self): return self._quality * self._item_price ``` 個人覺得這個例子並不是很恰當 - 在沒有改善可讀性的情況下,引入了重複呼叫帶來的開銷 - 有時也會有問題,原始的程式碼```base_price```一旦計算後是不會發生變化的,都提取成query之後就不能保證了 個人認為,即使為了解決temp只在函式內部生效而無法複用的問題,也應該改成: ``` def base_price(self): return self._quality * self._item_price def calc_price(self): base_price = self.base_price() if base_price > 1000: return base_price * 0.95 else: return base_price * 0.98 ``` 對於python,query還可以實現為property的形式,如果確定query的結果是固定的,還可以使用cached_porperty優化。 **introduce explaining variable** 將複雜表示式變成一個解釋性的區域性變數,解決可讀性問題 **split temporary variable** 一段程式碼中,一個臨時變數只能代表一個意思,否則應使用不同的臨時變數。 **remove assignment to parameter** 移除對引數的賦值,防止誤改、不小心的覆蓋,可讀性更好 - 不要對引數進行賦值,以一個臨時變數取代引數的位置 - java只採用pass by value傳遞方式。對於基本型別,同C++一樣;對於引用型別,可以改變引數內部的狀態(呼叫者實參的內部狀態隨之改變),但對引數重新賦值沒有任何意義。 - 可以給引數強制加上final修飾符,保證引數不被賦值 ## 在物件之間搬移特性 **move method** 遷移的過程中可能需要用到source class的特性(成員變數或者成員方法)。處理方式: 1. 將這個特性移到target class中; 1. 在target class中建立一個對source class的引用; 1. 將source object作為一個引數傳遞給target method(eclipse中的move就是該方法); 1. 將特性作為引數傳遞給target method **movie field** 常常是```extract class```的一部分,先移動field,在移動method **extract class** 先 move field,再move 必要的 method 需要考慮的是,新的類要不要對外公佈 **inline class** **Hide delegate** eg: ``` value = AObject.getBObject().getVlaue() ``` 到 ``` public int AObject::getValue(){ return bObject.getgetVlaue()} value = AObject.getVlaue() ``` 而且應該考慮要不要幹掉 AObject.getBObject **remove middle man** 與```Hide delegate```相反,如果一個server全是各種簡單委託 **introduce foreign method** 需要呼叫的類缺少一個你需要的方法 良好的建議在於:這個方法應該屬於服務類,因此只需將類的物件作為第一個引數就行,(其他引數應該是服務類 “新方法”的引數) **introduce local extension** - 已有且不能修改的類無法完成需求 - 使用繼承或者組合解決 ## 重新組織資料 **self encapsulate field** 對屬性的訪問通過getter和setter實現 適用情況: - 可能對屬性訪問做控制 - 可能會有subclass,且subclass的getter、setter方法不同於superclass **replace array with object** 一個數組,其中的元素表示不同的東西 ![](https://img2020.cnblogs.com/blog/1089769/202005/1089769-20200515134304483-425018214.png) **duplicated observed date** 有一些domain data(業務處理邏輯相關的)置身於GUI控制元件中,而domain method需要訪問之。 domain class 和GUI呈現分離,共享的資料通過觀察者模式實現同步控制 **replace magic number with symbolic constant** magic number 真的是人見人恨 **encapsulate collection** 如果函式返回一個集合,那麼這個返回值應該是隻讀的,而且不應該提供群集合的 setter 方法,而應提供加入、刪除集合元素的方法 Java中的```unmodifiable```系列就是返回只讀集合 **replace record with data class** record 比如來自資料庫,用一個 dataclass 將所有 field 宣告為 private ,提供對應的訪問函式 **replace type code with class** 型別編碼(type code)是一些常量或變數,一般有多個可能的值。普通常量使用的時候缺乏型別檢查,類似C++中的define,而class強加型別檢查。 比如血型如果用4個整數(c語言中的enum)表示,那麼是傳參的時候無法限制類型別,可讀性也差。C++11中enum class就解決了這個問題 前提是型別碼不會用於switch中,否則就得使用下面的重構手法 **replace type code with subclass** 如圖所示: ![](https://img2020.cnblogs.com/blog/1089769/202005/1089769-20200515134434014-1419342992.png) type code影響到了其所在類的行為, 那麼就得使用多型,該方法為```replace conditional with polymorphism```做準備。 前提是type code在物件建立的時候就確定,且宣告週期內不可變。如果 type code可能是變化的,只能使用```replace type code with state/strategy``` **replace type code with state/strategy** 和```replace type code with subclass```一樣,都是為```replace conditional with polymorphism```做準備 ## 簡化條件表示式 **Decompose conditional** 從if,then,else三個段落中提煉出獨立函式,使程式碼更加清晰 ![](https://img2020.cnblogs.com/blog/1089769/202005/1089769-20200515134418017-234727570.png) **consolidate conditional expression** 一系列條件測試如果得到的是相同的結果,那麼將這些條件合併為一個表示式,並將這個表示式提煉為一個獨立函式。```extract method```也更好體現了做什麼,而不是怎麼做。 如果這些條件邏輯上本來是彼此獨立的,那麼不應該使用本項重構 **consolidate duplicated conditional Fragments** 在條件分支上有相同的一段程式碼,那麼應該將這一段程式碼移到條件式之外,這是經常遇到的情況。 關鍵是這樣**更好體現了哪些是隨條件變化而變化的**,同時避免 duplicated code。 **remove control flag** 在迴圈的布林表示式中,某個變數起控制標記,如 ```while(exit)``` ,以break語句或者return語句代替控制語句 **replace nested conditional with guard clause** **衛語句**(guard clause):如果某一條件極其罕見,就應該單獨檢查該條件,並在該條件為真時立刻返回,這樣的單獨檢查成為衛語句。當然,我更喜歡稱之為early return,往往能減少巢狀的深度,讓程式碼可讀性更好。 本質:給予某一條件特別的重視(if then else表示對分支的重視是相同的),衛語句表示:一旦這種情況發生,應該做一些必要的清理工作,然後退出。 **replace conditional with polymorphism** 將一個條件表示式的分支放進一個subclass的覆寫函式內,並將原始函式宣告為抽象函式 關於對```replace type code with state/strategy```和```replace type code with subclass```的選擇:核心在於 type code 是否可能會在物件的生命週期內改變。 **introduce null object** 如果需要再三檢查一個物件是不是null,那麼以一個```null object```替換為null時的情況。null object 一般是常量,可以用 singleton 封裝,其屬性不會發生改變 需要注意的是: - 只在有大多數的客戶程式碼需要 null object 做出相應相應時,才有必要使用 null object。當然,如果少數地方需要做出不同響應,那麼也可以用```object.isNull```區分 - null object 是邏輯上可能出現的,是一種特殊情況,並不是異常。比如書中的例子:一個出租房確實可能暫時沒有租客。 **introduce assertion** assertion 應該是一個永遠為真的表示式,如果失敗,表示程式出了錯誤。assert既可以幫助排查bug,也可以幫助讀者**理解程式碼作者的假設、約束** ## 簡化函式呼叫 **rename method** **add parameter** 需要考慮增加引數是否會導致壞味道,```long parameter list```。如果可以通過已有的引數、屬性獲得新引數的值,那麼就不應該增加。 **remove parameter** 重構的時候要注意多型(繼承)的情況,不要遺漏。上同 **separate query from modifier** 將查詢操作和修改操作分開 **parameterize method** 若干函式做了類似的工作,只是函式本體中包含了不同的值。將導致函式差異的值作為引數傳入,如下面的程式碼: ``` def tenPercentRaise(self): self._salary *= 1.1 def fivePercentRaise(self): self._salary *= 1.05 # 重構後的程式碼 def raiseWithFactor(self, factor): self._salary *= (1 + factor) ``` 重構後,只保留一個方法```raiseWithFactor```,但新函式應該加上引數合法性的檢查。個人認為,如果```factor```的取值固定為少數的幾個值,那麼提供不同的介面也是可以的,只不過對外介面統一呼叫同一個私有介面。 **replace parameter with explicit methods** 函式的操作完全取決於引數值,則針對引數的每個引數值,建立一個獨立的函式 壞味道很明顯,引數是離散的,函式內以條件式檢查這些引數值,並根據不同引數值做出不同反應。比如下面這種型別的程式碼 ``` def setSwitch(self, is_on): if is_on: # do a lot of thing let switch on else: # do a lot of thing let switch off ``` **preserve whole object** 解決```long parameter list```的一種重構手法 **replace parameter with methods** 物件呼叫某個函式,將其返回值作為引數傳遞給另一個函式,而後面一個函式也可以呼叫前一個函式 。那麼在後一個函式中取出該項引數,並直接呼叫前面一個函式。動機在於如果可以通過非引數列表的方式獲得引數值,那麼就不要使用引數列表 使用前提 - 引數計算過程(即前一個函式)不會依賴呼叫端的某個引數 - 引數的存在可能是為了將來的彈性時也不能使用本項重構 **introduce parameter object** 某些引數總是很自然地同時出現----把這些引數抽象為一個物件,比如經常遇到的是以一對值代表一個範圍,如(start、end)、(lower、upper),用Range取代之 好處: - 縮減引數列表長度; - 更易理解和修改; - 可以把一些反覆進行的計算移到物件裡面(比如計算range差值) **remove setting method** 如果class中某個屬性在初始化的時候就設定,以後就不在改變了,那麼應該去掉改屬性的setter,還可以將屬性設定為final(or const)。 **hide method** 一個函式,從來沒有被其它class使用過,那麼它應該為private。最小化對外介面,需要的時候再開放。 **replace constructor with factory method** 常常在多型 或者```replace type code with subclass```中使用。可以用來實現```change value to reference```,或者單例。同時,工廠方法也會比建構函式過載可讀性更好 **encapsulate downcast** 不要讓使用者對你的函式返回值進行downcast,返回使用者需要的型別 這是低版本Java的問題(沒有模板),Java5.0之後沒問題了。這也是強型別OO語言的問題,python就沒有這個問題。 **replace error code with exception** [錯誤,異常與自定義異常](https://www.cnblogs.com/xybaby/p/11645885.html) 這篇文章對error code 和 exception 有較多討論 **replace exception with test** exception 不應該作為流程控制的手段,如果某種情況的出現不是意料之外的,那麼就不應該丟擲異常 ## 處理繼承體系 **pull up field** 把子類重複的屬性移動到基類 **pull up method** 把子類重複的函式移動到基類,如果兩個子函式相似但不盡相同,可以考慮使用form template method **pull up constructor body** 各個子類的建構函式程式碼有一樣的部分,則在基類中建立建構函式,在子類中呼叫 **push down method** **push down field** **extract subclass** class中一些特性只被某些實體使用,那麼新建一個subclass中,將這些特性轉移到subclass 需要考慮到extract class與extract subclass的區別(委託與繼承) **extract superclass** 兩個類有相似特性,把相似的部分移動到superclass,有時候為了程式碼複用也可以這麼做 **collapse hierarchy** 基類與子類沒有太大區別----合併之 **form template method** **replace inheritance with delegation** 某個subclass只使用superclass的一部分,或是根本不需要繼承而來的資料,則可以改繼承為委託。 繼承和委託也是adapter模式的兩種實現方式,本人也傾向於delegation **replace Delegation with inheritance** 太多的簡單委託關係 # 關於引入新技術、新思想的思考 即使一個新技術(新思想)已經經過社群的驗證,要引入到開發團隊來也不是一件容易的事情,也會遇到重重阻力: - 大多數人還不知道如何使用這項新技術 - 引入新技術的收益要長期才能看出來,那麼何必現在去付出呢?如果回報週期過長,那麼在收穫的時候可能已經不再當前的位置了 - 新技術的引入並不是非用不可,還需要花掉一些時間,老闆(專案經理)願意嗎? - 在線上專案使用新技術,反而可能引入BUG,冒險是否值得? 如果你本身就是老闆(技術Leader),且能夠頂住來自產品的壓力,那麼可以強推一項新技術,雖然強推效果也不一定好。但如果是作為平級,怎麼推廣呢?如何解決這些障礙? 第一:培訓與工具,通過培訓、分享讓團隊快速掌握新技術,使用工具讓成員掌握新思想。比如,想要遵守同一套程式碼規範,那麼最好配上相應的程式碼檢查。 第二:展現短期、肉眼可見的利益,新技術不僅要有長期收益,還得在短期內就展現出其優點。如果短期內就能看到好處,大家就願意去積極嘗試。 第三:降低開銷,降低上手難度,技術的投入也是講究投入產出比的,使用成本越低,大家就不會排斥。 第四:安全過渡,逐步進行,如果是線上專案,最好能有健全的回滾機制。 本質上,都是通過向你的Leader或者小夥伴展示,這個新東西又好又不貴,使用起來還很方便。 更有意思的是,書中提到Geoffrey Moore提出來的技術接納曲線: ![](https://img2020.cnblogs.com/blog/1089769/202005/1089769-20200515134630334-1211320488.png) 一個思想、技術、產品即使有先行者、嚐鮮者的支援,但想要大眾市場接受,還要跨過一條鴻溝。鴻溝的存在源於不同人的不同訴求:先行者關注的是新技術本身,而普羅大眾關注的是成熟度、引入(使用)成本。 在構建之法也有很詳細的分析。 # Reference [重構 - 改善既有程式碼的設計](https://book.douban.com/subject/1229923/) [程式碼整潔之道](https://book.douban.com/subject/4199741/) [什麼是整潔的程式碼](https://www.cnblogs.com/xybaby/p/11335829.html) [軟體架構模式](https://www.cnblogs.com/xybaby/p/11918485.html) [錯誤,異常與自定義異常](https://www.cnblogs.com/xybaby/p/11645885.html)