### 重構起源何處?
我曾經努力想找出重構(refactoring) 一詞的真正起源,但最終失敗了。優秀程序員肯定至少會花一些時間來清理自己的代碼。這么做是因為,他們知道簡潔的代碼比雜亂無章的代碼更容易修改,而且他們知道自己幾乎無法一開始就寫出簡潔的代碼。
重構不止如此。本書中我把重構看做整個軟件開發過程的一個關鍵環節。最早認識重構重要性的兩個人是Ward Cunningham 和 Kent Beck,他們早在1980s之前就開始使用Smalltalk,那是個特別適合重構的環境。Smalltalk是一個十分動態的環境,你可以很快寫出極具功能的軟件。Smalltalk的「編譯/連結/執行」周期非常短,因 此很容易快速修改代碼。它是面向對象,所以也能夠提供強大工具,最大限度地將修改的影響隱藏于定義良好的接口背后。Ward和Kent努力發展出一套適合這類環境的軟件開發過程〔如今Kent把這種風格叫作極限編程[Beck,XP])。他們意識到:重構對于提高他們的生產力非常重要。從那時起他們就一直在工作中運用重構技術,在嚴肅而認真的軟件項目中使用它,并不斷精煉這個程序。
Ward和Kent的思想對Smalltalk社群產生了極大影響,重構概念也成為Smalltalk文化中的一個重要元素。Smalltalk社群的另一位領袖是Ralph Johnson,伊利諾斯大 學烏爾班納分校教授,著名的「四巨頭」[3][Gang of Four]之一。Ralph最大的興趣之一就是開發軟件框架(framework)。他揭示了重構對于靈活高效框架的開發幫助。
[3]譯注:Ralph Johnson和另外三位先生Erich Gamma,Richard Helm, John Vissides合寫了軟件開發界馳名的《Design Patterns》,人稱四巨頭(Gang of Four)。
Bill Opdyke是Ralph的博士研究生,對框架也很感興趣。他看到重構的潛在價值,并看到重構應用于Smalltalk之外的其他語言的可能性。他的技術背景是電話交換系統的開發。在這種系統中,大量的復雜情況與時俱增,而且非常難以修改。Bill的博士研究就是從工具構筑者的角度來看待重構。通過研究,Bill發現:在C++ framework開發項目中,重構很有用。他也研究了極有必要的「語義保持性 (semantics-preserving)重構」及其證明方式,以及如何以工具實現重構。時至今日,Bill的博士論文[Opdyke]仍然是重構領域中最有價值、最豐碩的研究成果。此外他為本書撰寫了第13章。
我還記得1992年OOPSLA大會上見到Bill的情景。我們坐在一間咖啡廳里,討論當時我正為保徤業務構筑的一個概念框架(conceptual framework)中的某些工作。Bill跟我談起他的研究成果,我還記得自己當時的想法:『有趣,但并非真的那么重要。』唉,我完全錯了。
John Brant 和 Don Roberts 將重構中的「工具」構想發揚光大,開發了 一個名為「重構瀏覽器」(Refactoring Browser)的Smalltalk重構工具。他們撰寫了本書第14 章,其中對重構工具做了更多介紹。
那么,我呢?我一直有清理代碼的傾向,但從來沒有想到這會有那么重要。后來我和Kent一起做了個項目,看到他使用重構手法,也看到重構對生產性能和產品質量帶來的影響。這份體驗讓我相信:重構是一門非常重要的技術。但是,在重構的學習和推廣過程中我遇到了挫折,因為我拿不出任何一本書給程序員看,也沒有任何一位專家打算寫出這樣一本書。所以,在這些專家的幫助下,我寫下了這本書。
**優化一個薪資系統**
—— Rich Carzaniti
將 Chrysler Comprehensive Compensation(克萊斯勒綜合薪資系統)交給GemStone公司之前,我們用了相當長的時間開發它。開發過程中我們無可避免地發現程序不夠快,于是找了Jim Haungs——GemSmith中的一位好手——請他幫我們優化這個系統。
Jim先用一點時間讓他的團隊了解系統運作方式,然后以GemStone的ProfMonitor特性編寫出一個性能量測工具,將它插入我們的功能測試中。這個工具可以顯示系統產生的對象數量,以及這些對象的誕生點。
令我們吃驚的是:創建量最大的對象竟然是字符串,其中最大的工作量則是反復產生12,000-byte的字符串。這很特別,因為字符串實在太大了,連GemStone慣用的垃圾回收設施都無法處理它。由于它是如此巨大,每當被創建出來,GemStone都會將它分頁(paging)至磁盤上。也就是說字符串的創建竟然用上了I/O子系統(譯注:分頁機制會動用I/O),而每次輸出記錄時都要產生字樣的字符串三次!
我們的第一個解決辦法是把一個12,000-bytes字符串緩存(cached)起來,這可解決一大半問題。后來我們又加以修改,將它直接寫入一個file stream,從而避免產生字符串。
解決了「巨大字符串」問題后,Jim的量測工具又發現了一些類似問題,只不過字符串稍微小一些:800-bytes,500-bytes...等等,我們也都對它們改用file stream,于是問題都解決了。
使用這些技術,我們穩步提高了系統性能。開發過程中原本似乎需要1,000小時以上才能完成的薪資計算,實際運作時只花40小時。一個月后我們把時間縮短到18小時。正式投入運轉時只花12小時。經過一年的運行和改善后,全部計算只需9小時。
我們最大的改進就是:將程序放在多處理器(multi-processor)計算機上,以多線程(multiple threads)方式運行。最初這個系統并非按照多線程思維來設計,但由于代碼有良好分解(well factored),所以我們只花三天時間就讓它得以同時運行多個線程了。現在,薪資的計算只需2小時。
在Jim提供工具使我們得以在實際操作中量度系統性能之前,我們也猜測過問題所在。但如果只靠猜測,我們需要很長的時間才能試出真正的解法。真實的量測指出了一個完全不同的方向,并大大加快了我們的進度。
- 譯序 by 侯捷
- 譯序 by 熊節
- 序言
- 前言
- 章節一 重構,第一個案例
- 起點
- 重構的第一步
- 分解并重組statement()
- 運用多態(Polymorphism)取代與價格相關的條件邏輯
- 結語
- 章節二 重構原則
- 何謂重構
- 為何重構
- 「重構」助你找到臭蟲(bugs)
- 何時重構
- 怎么對經理說?
- 重構的難題
- 重構與設計
- 重構與性能(Performance)
- 重構起源何處?
- 章節三 代碼的壞味道
- Duplicated Code(重復的代碼)
- Long Method(過長函數)
- Large Class(過大類)
- Long Parameter List(過長參數列)
- Divergent Change(發散式變化)
- Shotgun Surgery(散彈式修改)
- Feature Envy(依戀情結)
- Data Clumps(數據泥團)
- Primitive Obsession(基本型別偏執)
- Switch Statements(switch驚悚現身)
- Parallel Inheritance Hierarchies(平行繼承體系)
- Lazy Class(冗贅類)
- Speculative Generality(夸夸其談未來性)
- Temporary Field(令人迷惑的暫時值域)
- Message Chains(過度耦合的消息鏈)
- Middle Man(中間轉手人)
- Inappropriate Intimacy(狎昵關系)
- Alternative Classes with Different Interfaces(異曲同工的類)
- Incomplete Library Class(不完美的程序庫類)
- Data Class(純稚的數據類)
- Refused Bequest(被拒絕的遺贈)
- Comments(過多的注釋)
- 章節四 構筑測試體系
- 自我測試代碼的價值
- JUnit測試框架
- 添加更多測試
- 章節五 重構名錄
- 重構的記錄格式
- 尋找引用點
- 這些重構準則有多成熟
- 章節六 重新組織你的函數
- Extract Method(提煉函數)
- Inline Method(將函數內聯化)
- Inline Temp(將臨時變量內聯化)
- Replace Temp with Query(以查詢取代臨時變量)
- Introduce Explaining Variable(引入解釋性變量)
- Split Temporary Variable(剖解臨時變量)
- Remove Assignments to Parameters(移除對參數的賦值動作)
- Replace Method with Method Object(以函數對象取代函數)
- Substitute Algorithm(替換你的算法)
- 章節七 在對象之間搬移特性
- Move Method(搬移函數)
- Move Field(搬移值域)
- Extract Class(提煉類)
- Inline Class(將類內聯化)
- Hide Delegate(隱藏「委托關系」)
- Remove Middle Man(移除中間人)
- Introduce Foreign Method(引入外加函數)
- Introduce Local Extension(引入本地擴展)
- 章節八 重新組織數據
- Self Encapsulate Field(自封裝值域)
- Replace Data Value with Object(以對象取代數據值)
- Change Value to Reference(將實值對象改為引用對象)
- Replace Array with Object(以對象取代數組)
- Replace Array with Object(以對象取代數組)
- Duplicate Observed Data(復制「被監視數據」)
- Change Unidirectional Association to Bidirectional(將單向關聯改為雙向)
- Change Bidirectional Association to Unidirectional(將雙向關聯改為單向)
- Replace Magic Number with Symbolic Constant(以符號常量/字面常量取代魔法數)
- Encapsulate Field(封裝值域)
- Encapsulate Collection(封裝群集)
- Replace Record with Data Class(以數據類取代記錄)
- Replace Type Code with Class(以類取代型別碼)
- Replace Type Code with Subclasses(以子類取代型別碼)
- Replace Type Code with State/Strategy(以State/strategy 取代型別碼)
- Replace Subclass with Fields(以值域取代子類)
- 章節九 簡化條件表達式
- Decompose Conditional(分解條件式)
- Consolidate Conditional Expression(合并條件式)
- Consolidate Duplicate Conditional Fragments(合并重復的條件片段)
- Remove Control Flag(移除控制標記)
- Replace Nested Conditional with Guard Clauses(以衛語句取代嵌套條件式)
- Replace Conditional with Polymorphism(以多態取代條件式)
- Introduce Null Object(引入Null 對象)
- Introduce Assertion(引入斷言)
- 章節十一 處理概括關系
- Pull Up Field(值域上移)
- Pull Up Method(函數上移)
- Pull Up Constructor Body(構造函數本體上移)
- Push Down Method(函數下移)
- Push Down Field(值域下移)
- Extract Subclass(提煉子類)
- Extract Superclass(提煉超類)
- Extract Interface(提煉接口)
- Collapse Hierarchy(折疊繼承關系)
- Form Template Method(塑造模板函數)
- Replace Inheritance with Delegation(以委托取代繼承)
- Replace Delegation with Inheritance(以繼承取代委托)
- 章節十二 大型重構
- 這場游戲的本質
- Tease Apart Inheritance(梳理并分解繼承體系)
- Convert Procedural Design to Objects(將過程化設計轉化為對象設計)
- Separate Domain from Presentation(將領域和表述/顯示分離)
- Extract Hierarchy(提煉繼承體系)
- 章節十三 重構,復用與現實
- 現實的檢驗
- 為什么開發者不愿意重構他們的程序?
- 現實的檢驗(再論)
- 重構的資源和參考資料
- 從重構聯想到軟件復用和技術傳播
- 結語
- 參考文獻
- 章節十四 重構工具
- 使用工具進行重構
- 重構工具的技術標準(Technical Criteria )
- 重構工具的實用標準(Practical Criteria )
- 小結
- 章節十五 集成
- 參考書目