重構:幹掉有壞味道的代碼

第一次讀重構 - 改善既有代碼的設計(Refactoring: Improving the Design of Existing Code)這本書仍是在學校的時候,那個時候剛開始寫Java代碼,師兄推薦了兩本書《重構》、《設計模式》。在今日看來,這兩本書都是經典好書,得謝謝個人師兄。html

最近,打算從新讀一下這兩本書,先讀了重構,感受仍是收穫頗多。想來這兩本書都是比較偏向實踐的,並非讀一遍就能夠束之高閣了,而是應該常讀常新。java

本文地址:http://www.javashuo.com/article/p-tptsqlsz-dp.htmlpython



去年重讀了代碼整潔之道這本書,也寫了一篇筆記 什麼是整潔的代碼。今年重讀《重構》的時候,發現書中不少內容都是相同的,做者好像都是叫 Martin 什麼的,我還想難道是同一我的?程序員

查了一下,並非,重構的做者是 Martin Fowler;而clean code的做者是 Robert C. Martin ,江湖人稱 "Uncle Bob"。數據庫

不過好像兩位都在面向對象、敏捷領域有所建樹。By the way,重構的初版寫於1999年,(本文也基於初版的譯文),而clean code的初版寫於2009年,且clean code是有參考 「refactoring: Improving the Design of Existing Code」的。express

在我看來,重構這本書的核心價值有三部分:編程

  • 指出有「壞味道」的代碼
  • 對這種代碼給出重構的詳細步驟,這些步驟保證重構過程是安全的
  • 關於引入新技術、新思想的一些思考,如重構、代碼複用、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一塊兒閱讀

另外,我的以爲本節還有一個嚴重問題:那就是缺少例子。「壞味道」是咱們爲何要重構,然後面的具體手法是如何重構,why 比 how 更重要些,因此我的感受應該在描述"壞味道"的時候給出代碼示例。

重複的代碼 -- duplicated code

最簡單的狀況,就是兩段代碼有相同的表達式語句,處理方法也很明確,那就是extract method,而後應用這個新的方法。另一種常見狀況,就是這兩段相同的代碼位於不一樣的子類 -- 每每是新增子類的時候部分複製了其餘子類的代碼,這個時候就應該使用pull up method將公共代碼抽取到基類去。

固然,兩段代碼也多是類似但不徹底相同,那麼能夠考慮將差別部分子類化,即便用form template method。或者將差別部分參數化,即經過參數控制不一樣的邏輯,但須要注意的是,參數會不會致使兩種大相徑庭的行爲,即parameterize methodreplace parameter with explicit methods的區別。

最後,也常常發現兩個類之間有相同的重複代碼,可是兩者之間並無繼承關係(並非is-a關係),那麼能夠extract class將公共部分提取出來,以組合的方式使用,或者使用多繼承--Mixin 繼承其實現。

過長的函數 -- long method

過長的函數每每冗雜着過多的細節,在什麼是整潔的代碼一文就曾經中, 代碼的組織應該像金字塔同樣,「每一個函數一個抽象層次,函數中的語句都要在同一個抽象層級,不一樣的抽象層級不能放在一塊兒」。

對於過長的函數,負責任的代碼做者每每會給出一些註釋:解釋某一小段代碼的做用,這其實就暗示着咱們能夠把這段代碼移到一個獨立的函數,而後取一個恰當的名字來展示其意圖。這個新函數的名字應該體現作什麼,而不是怎麼作,這樣,新函數的名字就能夠取代原來的註釋。

若是新抽取出來的子函數須要用到原函數中的參數或者臨時變量,那麼這些都須要參數化到子函數,這可能致使子函數參數列表過長的問題,這個問題及其解決辦法在後面闡述。

除了註釋,還有什麼「味道」暗示應該提取子函數呢,好比 if then else中有大段的代碼,這個時候可使用Decompose conditional處理條件表達式。

過大類 -- large class

單個類有太多的實例屬性,並且其中某些屬性常常獨立於其餘屬性一塊兒使用,那麼可使用extract class

好比一個課程信息類 Course,裏面包含了 CourseId、CourseName、TeacherId、TeacherName、TeacherSex 等屬性,那麼壞味道就是:不少屬性名擁有相同的前綴。所以能夠經過extrace classCTeacherId、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
  2. move method
  3. replace type code with subclass(strategy、state)
  4. 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。這有點相似軟件架構模式中提到的污水池反模式(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中;
  2. 在target class中創建一個對source class的引用;
  3. 將source object做爲一個參數傳遞給target method(eclipse中的move就是該方法);
  4. 將特性做爲參數傳遞給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

一個數組,其中的元素表示不一樣的東西

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

如圖所示:

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三個段落中提煉出獨立函數,使代碼更加清晰

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/strategyreplace 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

錯誤,異常與自定義異常 這篇文章對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提出來的技術接納曲線:

一個思想、技術、產品即便有先行者、嚐鮮者的支持,但想要大衆市場接受,還要跨過一條鴻溝。鴻溝的存在源於不一樣人的不一樣訴求:先行者關注的是新技術自己,而普羅大衆關注的是成熟度、引入(使用)成本。

在構建之法也有很詳細的分析。

Reference

重構 - 改善既有代碼的設計
代碼整潔之道
什麼是整潔的代碼
軟件架構模式
錯誤,異常與自定義異常

相關文章
相關標籤/搜索