摘要:為何重構重構有四大好處重構改進軟件設計如果沒有重構,程序的設計會逐漸腐敗變質。經常性的重構可以幫助維持自己該有的形態。你有一個大型函數,其中對局部變量的使用使你無法采用。將這個函數放進一個多帶帶對象中,如此一來局部變量就成了對象內的字段。
哪有什么天生如此,只是我們天天堅持。 -Zhiyuan
國慶抽出時間來閱讀這本從師傅那里借來的書,聽說還是程序員的必讀書籍。
關于書的高清下載連接會在文章的最后呈現。
話不多說讓我們一起去領略一下書中風采。
如果你讀到這篇文章,說明你也想對你的代碼進行重構,那首先我們就來看一下什么是重構?
1. 何謂重構?兩個定義:
名詞形式
重構(名詞):對軟件內部的一種太縱橫,目的是在不改變軟件可觀察的前提下,提高其可理解性,降低其修改成本。
動詞形式
重構(動詞):使用一系列的重構手法,在不改變軟件可觀察的行為的前提下,調整其結構。
上面是重構的學術解釋,相信一些大神是可以直接一步到位的領略它的意思。我比較喜歡通俗易懂的解釋:
你要問我“重構就只是整理代碼嗎?” 在某種角度來說 還真是。 but ! 重構肯定有其獨到的地方:
它還提供一種 更高效 且 受控的 代碼整理技術。
劃重點哈!
作者還對重構進行了另外兩方面的擴展。讓我一起來看下還有什么好玩的:
重構的目的是是軟件更容易被理解和修改。
也就是說讓你的修改但你只能對軟件的可觀察的外部行為造成很小的變化甚至不造成變化。 和重構形成對比的是 **性能優化** (我反正一開始覺得這兩個都是一樣的), 差就差在性能優化通常不會改變組件的行為(除了執行速度)指揮改變其內部結構。 但是! 這兩個的出發點不同: **性能優化往往是代碼較難理解,但是為了得到所需的性能你不得不這么做。**
重構不會改變軟件的可觀察的行為
也就是說重構之后軟件的功能一如以往。所有人除了你自己都不知道有東西改變過(就像鬼子進村悄悄的進行,打槍的不要)。2. 為何重構?
重構有四大好處:
重構改進軟件設計
如果沒有重構,程序的設計會逐漸腐敗變質。重構很像是在整理代碼,你所做的就是讓所有東西回到應出的位置上。經常性的重構可以幫助維持自己該有的形態。
重構使軟件更容易理解
重構可以幫助我們讓代碼更易讀。
重構幫助找到bug
對代碼進行重構,可以幫助我們深入理解代碼,對代碼理解的越深,就越能幫我們找到bug。重構能夠幫大家更有效地寫出強健的代碼。(這對程序員來說是重點)
重構提高編程速度
重構可以幫助我們更快速地開發軟件,因為它阻止系統腐敗變質,它甚至還可以提高設計質量。(這對Boss來說是重點)
3. 何時重構?Any fool can write code that a computer can understand. Good programmers write code that humans can understand.任何一個傻瓜都能寫出計算機可以理解的代碼。唯有寫出人類容易理解的代碼,才是優秀的代碼。
重構 不是 一件應該特別撥出時間做的事情,重構應該隨時隨地進行。不應該為重構而重構,之所以重構,是因為我們想做別的什么事,而重構可以幫助我們把那些事做好。
作者給出了一個三次原則,讓我們來看一下:
三次法則:事不過三,三則重構。
添加功能時重構。
修補錯誤時重構。
復審代碼時重構。
夠明確了吧! 當你發現你的代碼正符合里面的條件式那就抓緊時間重構吧。
4. 怎么對經理說?此章節只可意會不可言傳。
5. 重構的難題當你使用一種可以提高生成力的新技能時,一定要仔細思考此場景是不是適用。 別人的“好媳婦”在你著沒準就沒那么好用。
假如你發現你確實需要重構,請參考仔細閱讀下面你可能會遇到的難題:
數據庫難在哪?
1.絕大多數的程序都和背后的數據庫結構緊密的耦合在一起。 2.數據遷移
接口修改難在哪?
1.已經發布了的接口
何時不該重構?
1.代碼根本無法工作或者太糟糕,重構還不如重寫來的簡單。 2.在項目的最后期限,應該避免重構
中場休息
文章至此,大家應該知道自己現在所處的階段適不適合重構心里有點b數了吧?
不適合就別往下看了,光知道重構是什么東西就可以,以后用到再回來看看這篇文章。
下面的文章能告訴你怎么重構,只是框架,我又不可能把書都抄下來,如果大家真的需要重構不如立刻行動買書看,或者下載我的電子書。(免費的哦~)
6. 代碼的壞味道重復代碼(Duplicated Code)
過長函數(Long Method)
過大的類(Large Class)
過長參數列(Long Parameter List)
發散式變化(Divergent Change):一個類受多種變化的影響
霰彈式修改(Shotgun Surgery):一種變化引發多個類相應修改
依戀情結(Feature Envy):函數對某個類的興趣高過自己所處類的興趣
數據泥團(Data Clumps):相同的若干項數據出現在不同地方,這些綁在一起出現的數據應該有屬于它們自己的對象
基本類型偏執(Private Obsession):很多人不愿意在小任務上運用小對象
switch驚悚現身(Switch Statements):switch語句會在很多地方重復出現,一改則需全改
平行繼承體系(Parallel Inheritance Hierarchies):當你為某一個類增加子類時,也必須為另一個類相應增加一個類
冗贅類(Lazy Class):如果一個類不值得存在,那就讓它消失
夸夸其談的未來星(Speculative Generality):預留的無用的抽象類,無用的抽象參數
令人迷惑的暫時字段(Temporary Field):類中某個字段只為某些特殊情況而設置
過度耦合的消息鏈(Message Chains):用戶向一個對象請求另一個對象,然后再向后者請求另一個對象......
中間人(Middle Man):無用的委托,過多的中間層
狎昵關系(Inappropriate Intimacy):兩個類過于親密,一個類過于關注另一個類的成員
異曲同工的類(Alternative Classes with DifferentInterfaces):不同名字的類或函數,作者相同的事
不完美的庫類(Incomplete Library Class):類庫設計不可能完美
純數據類(Data Class):一個類擁有一些字段以及用于訪問這些字段的函數,除此之外一無長物
被拒絕的遺贈(Refused Bequest):子類不想繼承超類所有的函數和數據,只想挑幾樣來玩
過多的注釋(Comments)
7. 構筑測試體系重構的首要前提是擁有一個可靠的測試環境。
只要寫好一點功能,就立即添加測試,并確保所有測試都完全自動化,讓它們檢查自己的測試結果。一套測試就是一個強大的bug偵測器,能夠大大縮減查找bug所需要的時間。
撰寫測試代碼的最有用時機是在開始編程之前。當你需要添加特性的時候,先寫相應測試代碼。
多運用單元測試。測試你最擔心出錯的地方,考慮可能出錯的邊界條件。不要因為測試無法捕捉所有bug就不寫測試,因為測試的確可以捕捉到大多數bug。“花合理時間抓出大多數bug”要好過“窮盡一生抓出所有bug”。
8. 重新組織函數提煉函數(Extract Method)。你有一段代碼可以被組織在一起并獨立出來。將這段代碼放進一個獨立函數中,并將函數名稱解釋該函數的用途。
內聯函數(Inline Method)。一個函數的本體與名稱同樣清楚易懂。在函數調用點插入函數本體,然后移除該函數。
內聯臨時變量(InlineTemp)。你有一個臨時變量,只被一個簡單表達式賦值一次,而它妨礙了其他重構手法。將所有對該變量的引用動作,替換為對它賦值的那個表達式自身。
以查詢取代臨時變量(Replace Temp withQuery)。你的程序以一個臨時變量保存某一表達式的運算結果。將這個表達式提煉到一個獨立函數中。將這個臨時變量的所有引用點替換為對新函數的調用。此后,新函數就可被其他函數使用。
引入解釋性變量(Introduce ExplainingVariable)。你有一個復雜的表達式。將該復雜表達式(或其中一部分)的結果放進一個臨時變量,以此變量名稱來解釋表達式用途。
分解臨時變量(Split TemporaryVariable)。你的程序有某個臨時變量被賦值過一次,它既不是循環變量,也不被用于收集計算結果。針對每次賦值,創造一個獨立、對應的臨時變量。
移除對參數的賦值(Remove Assignments Parameters)。代碼對一個參數進行賦值。以一個臨時變量取代參數的位置。
以函數對象取代函數(Replace Method with MethodObject)。你有一個大型函數,其中對局部變量的使用使你無法采用ExtractMethod。將這個函數放進一個多帶帶對象中,如此一來局部變量就成了對象內的字段。然后你可以在同一個對象中將這個大型函數分解為多個小型函數。
替換算法(Substitute Algorithm)。你想要把某個算法替換為另一個更清晰的算法。將函數本體替換為另一個算法。
9. 在對象之間搬移特性搬移函數(MoveMethod)。你的程序中,有個函數與其所駐之外的另一個類進行更多交流:調用后者,或被后者調用。在該函數最常引用的類中建立一個有著類似行為的新函數。將舊函數變成一個單純的委托函數,或是將舊函數完全移除。
搬移字段(MoveField)。你的程序中,某個字段被其所駐類之外的另一個類更多地用到。在目標類新建一個字段,修改源字段的所有用戶,令它們改用新字段。
提煉類(Extract Class)。某個類做了應該有兩個類做的事。建立一個新類,將相關的字段和函數從舊類搬移到新類。
將類內聯化(Inline Class)。某個類沒有做太多事情。將這個類的所有特性搬移到另一個類中,然后移除原類。
隱藏“委托關系”(Hide Delegate)。客戶通過一個委托來調用另一個對象。在服務類上建立客戶所需的所有函數,用以隱藏委托關系。
移除中間人(Remove Middle Man)。某個類做了過多的簡單委托動作。讓客戶直接調用受托類。
引入外加函數(Introduce ForeignMethod)。你需要為提供服務的類增加一個函數,但你無法修改這個類。在客戶類中建立一個函數,并以第一參數形式傳入一個服務類實例。
引入本地擴展(Introduce LocalExtension)。你需要為服務類提供一些額外函數,但你無法修改這個類。建立一個新類,使它包含這些額外函數。讓這個擴展品成為源類的子類或包裝類。
10. 重新組織數據自封裝字段(Self Encapsulate Field)。你直接訪問一個字段,但與字段之間的耦合關系逐漸變得笨拙。為這個字段建立取值/設值函數,并且只以這些函數來訪問字段。
以對象取代數據值(Replace Data Value withObject)。你有一個數據項,需要與其他數據和行為一起使用才有意義。將數據項變成對象。
將值對象改為引用對象(Change Value toReference)。你從一個類衍生出許多彼此相等的實例,希望將它們替換為同一個對象。將這個值對象變成引用對象。
將引用對象改為值對象(Change Reference to Value)。你有一個引用對象,很小且不可變,而且不易管理。將它變成一個值對象。
以對象取代數據(Replace Array withObject)。你有一個數組,其中的元素各自代表不同的東西。以對象替換數組,對于數組中的每個元素,以一個字段來表示。
復制“被監視數據”(Duplicate ObservedData)。你有一些領域數據置身GUI控件中,而領域函數需要訪問這些數據。將該數據復制到一個領域對象中。建立一個Observe模式,用以同步領域對象和GUI對象內的重復數據。
將單向關聯改為雙向關聯(Change Unidirectional Association toBidirectional)。兩個類都需要使用對方特性,但其間只有一條單向鏈接。添加一個反向指針,并使修改函數能夠同時更新兩條鏈接。
將雙向關聯改為單向關聯(Change Bidirectional Association toUnidirectional)。兩個類之間有雙向關聯,但其中一個類如今不再需要另一個類的特性。去除不必要的關聯。
以字面常量取代魔法數(Replace Magic Number with SymbolicConstant)。你有一個字面數值,帶有特別含義。創造一個常量,根據其意義為它命名,并將上述的字面數值替換為這個常量。
封裝字段(Encapsulate Field)。你的類中存在一個public字段。將它聲明為private,并提供相應的訪問函數。
封裝集合(EncapsulateCollection)。有個函數返回一個集合。讓這個函數返回該集合的一個只讀副本,并在這個類中提供添加/移除集合元素的函數。
以數據類取代記錄(Replace Record with Data Class)。你需要面對傳統編程環境中的記錄結構。為該記錄創建一個“啞”數據對象。
以類取代類型碼(Replace Type Code withClass)。類之中有一個數值類行碼,但它并不影響類的行為。以一個新的類替換該數值類型碼。
以子類取代類型碼(Replace Type Code withSubclass)。你又一個不可變的類型碼,它會影響類的行為。以子類取代這個類型碼。
以State/Strategy取代類型碼(Replace Type Code withState/Strategy)。你有一個類型碼,它會影響類的行為,但你無法通過繼承手法消除它。以狀態對象取代類型碼。
以字段取代子類(Replace Subclass withFields)。你的各個子類的唯一差別只在“返回常量數據”的函數身上。修改這些函數,使他么返回超類中的某個(新增)字段,然后銷毀子類。
11. 簡化條件表達式分解條件表達式(DecomposeConditional)。你有一個復雜的條件(if-then-else)語句。從if、then、else三分段落中分別提煉出獨立函數。
合并條件表達式(Consolidate ConditionalExpression)。你有一系列條件測試,都得到相同結果。將這些測試合并為一個條件表達式,并將這個條件表達式提煉成為一個獨立函數。
合并重復的條件片段(Consolidate Duplicate ConditionalFragments)。在條件表達式的每個分支上有著相同的一段代碼。將這段重復的代碼搬移到條件表達式之外。
移除控制標記(Remove ControlFlag)。在一系列布爾表達式中,某個變量帶有“控制標記”的作用。以break語句或return語句取代控制標記。
以衛語句取代嵌套條件表達式(Replace nested Conditional with GuardClauses)。函數中的條件邏輯使人難以看清正常的執行路徑。使用衛語句表現所有的特殊情況。
以多態取代條件表達式(Replace Conditional withPolymorphism)。你手上有個條件表達式,它根據對象類型的不同選擇不同的行為。將這個條件表達式的每個分支放進一個子類內的覆寫函數中,然后將原始函數聲明為抽象函數。
引入Null對象(Introduce Null Object)。你需要再三檢查某對象是否為null。將null值替換為null對象。
引入斷言(Introduce Assertion)。某一段代碼需要對程序狀態做出某種假設。以斷言明確表現這種假設。
12. 簡化條件表達式函數改名(Rename Method)。函數的名稱未能揭示函數的用途。修改函數的名稱。
添加參數(Add Parameter)。某個函數需要從調用端得到更多信息。為此函數添加一個對象參數,讓該對象帶進函數所需信息。
移除參數(Remove Parameter)。函數本體不再需要某個參數。將該參數去除。
將查詢函數和修改函數分離(Separate Query from Modifier)。某個函數既返回對象狀態值,又修改對象狀態。建立兩個不同的函數,其中一個負責查詢,另一個負責修改。
令函數攜帶參數(ParameterizeMethod)。若干函數做了類似的工作,但在函數本體中卻包含了不同的值。建立單一函數,以參數表達那些不同的值。
以明確函數取代參數(Replace Parameter with ExplicitMethods)。你有一個函數,其中完全取決于參數值而采取不同行為。針對該參數的每一個可能值,建立一個獨立函數。
保持對象完整(Preserve WholeObject)。你從某個對象中取出若干值,將它們作為某一次函數調用時的參數。改為傳遞整個對象。
以函數取代參數(Replace Parameter withMethods)。對象調用某個函數,并將所得結果作為參數,傳遞給另一個函數。而接受該參數的函數本身也能夠調用前一個函數。讓參數接受者去除該項參數,并直接調用前一個函數。
引入參數對象(Introduce Parameter Object)。某些參數總是很自然地同時出現。以一個對象取代這些參數。
移除設值函數(Remove Setting Method)。類中的某個字段應該在對象創建時被設值,然后就不再改變。去掉該字段的所有設值函數。
隱藏函數(Hide Method)。有一個函數,從來沒有被其他任何類用到。將這個函數修改為private。
以工廠函數取代構造函數(Replace Constructor with FactoryMethod)。你希望在創建對象時不僅僅是做簡單的構建動作。將構建函數替換為工廠函數。
封裝向下轉型(Encapsulate Downcast)。某個函數返回的對象,需要由函數調用者執行向下轉型。將向下轉型動作移到函數中。
以異常取代錯誤碼(Replace Error Code withException)。某個函數返回一個特定的代碼,用以表示某種錯誤情況。改用異常。
以測試取代異常(Replace Exception withTest)。面對一個調用者可以預先檢查的條件,你拋出了一個異常。修改調用者,使它在調用函數之前先做檢查。
13.處理概括關系字段上移(Pull Up Field)。兩個子類擁有相同的字段。將該字段移至超類。
函數上移(Pull Up Method)。有些函數,在各個子類中產生完全相同的結果。將該函數移至超類。
構造函數本體上移(Pull Up ConstructorBody)。你在各個子類中擁有一些構造函數,他們的本體幾乎完全一致。在超類中新建一個構造函數,并在子類構造函數中調用它。
函數下移(Push Down Method)。超類中的某個函數只與部分(而非全部)子類有關。將這個函數移到相關的那些子類去。
字段下移(Push Down Field)。超類中的某個字段只被部分(而非全部)子類用到。將這個字段移到需要它的那些子類去。
提煉子類(ExtractSubclass)。類中的某些特性只被某些(而非全部)實例用到。新建一個子類,將上面所說的那一部分特性移到子類中。
提煉超類(Extract Superclass)。兩個類有相似特性。為這兩個類建立一個超類,將相同特性移至超類。
提煉接口(ExtractInterface)。若干客戶使用類接口中的同一子集,或者兩個類的接口有部分相同。將相同的子集提煉到一個獨立接口中。
折疊繼承體系(Collapse Hierarchy)。超類和子類之間無太大差別。將它們合為一體。
塑造模板函數(Form TemPlateMethod)。你有一些子類,其中相應的某些函數以相同順序執行類似的操作,但各個操作的細節上所有不同。將這些操作分別放進獨立函數中,并保持它們都有相同的簽名,于是原函數也就變得相同了。然后將原函數上移至超類。
以委托取代繼承(Replace Inheritance withDelegation)。某個子類只使用超類接口中的一部分,或是根本不需要繼承而來的數據。在子類中新建一個字段用以保存超類;調整子類函數令它改而委托超類;然后去掉兩者之間的繼承關系。
以繼承取代委托(Replace Delegation withInheritance)。你在兩個類之間使用委托關系,并經常為整個接口編寫許多極簡單的委托函數。讓委托類來繼承受托類。
14.大型重構**梳理并分解繼承體系(Tease ApartInheritance)。某個繼承體系同時承擔兩項責任。建立兩個繼承體系,并通過委托關系讓其中一個可以調用另一個。
將過程化設計轉化為對象設計(Convert Procedural Design toObjects)。你手上有一些傳統過程化風格的代碼。將數據記錄變成對象,將大塊的行為分成小塊,并將行為移入相關對象之中。
將領域和表述/顯示分離(Separate Domain fromPresentation)。某些GUI類之中包含了領域邏輯。將領域邏輯分離出來,為它們建立獨立的領域類。
提煉繼承體系(ExtractHierarchy)。你有某各類做了太多工作,其中一部分工作是以大量條件表達式完成的。建立繼承體系,以一個子類表示一種特殊情況。
有幸看這本書的朋友 請反復看 反復看 反復看!
希望看到這篇文章的人能有收獲,希望多年后翻看此篇文章的我能有更近一步的理解。 加油!
點贊 收藏 走一波。
高清pdf版本:鏈接: https://pan.baidu.com/s/1b69a2M 密碼: x9sf
請享用。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/67703.html
摘要:重構改善既有代碼設計動詞使用一系列重構手法,在不改變軟件可觀察行為的前提下,調整其結構。修補錯誤時重構代碼時重構怎么重構關于代碼的重構技巧參考重構改善既有代碼設計讀書筆記代碼篇個人博客 重構定義 名詞 對軟件內部結構的一種調整,目的是在不改變軟件可觀察行為的前提下,提高其可理解性,降低其修改成本。——《重構-改善既有代碼設計》 動詞 使用一系列重構手法,在不改變軟件可觀察行為的前提下,...
摘要:改進代碼設計的一個重要原則就是消除重復代碼使軟件更容易被理解優秀的代碼能夠讓接收你代碼的付出更少的學習成本。重構更容易找到重構能加深對代碼的理解。可以重構的情況添加功能時可以重構。說明你沒有發現代碼的錯誤。需要重構復審代碼時可以重構。 為何重構 重構不是銀彈,但是幫助你達到以下幾個目的 改進軟件設計 不良的程序需要更多的代碼。而代碼越多,正確的修改就越困難。改進代碼設計的一個重要原則就...
摘要:什么是重構列表重構方法需要以一種特定的格式記錄下來。這些重構手法到底有多成熟本書中提到的重構手法第章。做法創造新函數,以用途命名提煉代碼到函數中檢查變量名是否符合規范在源函數中,將被提煉代碼替換為函數引用測試范例重構前重構后 什么是重構列表 重構方法需要以一種特定的格式記錄下來。按照格式記錄下來的重構方法的集合叫重構列表 重構的記錄格式 每個重構手法可分為5個部分: 名稱 構建重構詞匯...
摘要:并根據目錄選讀第章重構,第一個案例這是只是一個方法。絕大多數情況下,函數應該放在它所使用的數據的所屬對象內最好不要在另一個對象的屬性基礎上運用語句。 什么是重構 在不改變代碼外在行為的前提下,對代碼做出修改以改進程序內部的結構簡單地說就是在代碼寫好后改進它的設計 誰該閱讀這本書 專業程序員(能夠提高你的代碼質量) 資深設計師和架構規劃師(理解為什么需要重構,哪里需要重構) 閱讀技巧...
摘要:前言重構要保證不會影響功能,所以測試很重要每個程序員應該都有花幾個小時改一小段代碼的故事。如果有一個完全的測試體系。不多說添加更多測試測試不光只測代碼是否跑通,還要考慮各種情況,代碼是否能做出正確的行為。還應該留下一個暴露的單元測試 前言 重構要保證不會影響功能,所以測試很重要ps:每個程序員應該都有花幾個小時改一小段代碼的故事。如果有一個完全的測試體系。 junit框架 java程序...
摘要:但條件邏輯也是不能忽視的分解條件表達式問題有一個復雜的條件語句。沒什么說的動機重構代碼就是錯移除控制標志問題在一系列布爾表達式中,某個變量帶有控制標記的作用方法以語句或語句取代控制標記動機控制標記大大降低了代碼可讀性。 前言 前面已經對類,方法,字段都進行了重構。貌似看起來很完整了。但條件邏輯也是不能忽視的 分解條件表達式 問題 有一個復雜的條件(if-then-else)語句。(判斷...
閱讀 787·2019-08-30 15:55
閱讀 1530·2019-08-30 15:52
閱讀 2695·2019-08-30 15:44
閱讀 2105·2019-08-30 11:14
閱讀 2621·2019-08-29 13:59
閱讀 1817·2019-08-29 13:45
閱讀 1012·2019-08-29 13:21
閱讀 3374·2019-08-26 13:31