2
、重構
????
重構的概念不是源自
Martin Fowler
這部名著《重構——改善既有代碼的設計》(《
Refactoring
:
Improving the Design of Existing Code
》),而是來自
Smalltalk
社區,感謝
Fowler
此書是用我最熟悉的
Java
來作為所舉實例的語言的,這使我閱讀此書比較省勁,而不像閱讀《設計模式》,畢竟對
C++
沒那么多經驗。這本書一共
15
章,我看到第
6
章。第
1
章重構,第一個案例,用
Step by Step
的方式完成了一系列重構;第
2
章:重構原則,介紹了什么是重構和一些與之相關的話題,大都是經驗之談,沒什么代碼;第
3
章讓我思考了很多,它主要介紹了代碼的壞味道,一共
22
種
Bad Smell
。其中大部分都是我在實際編程過程中遇到過或認真思考過的,因此就有一種遇到老朋友的欣喜,看得格外起勁。而后第
4
章主要討論了構筑測試體系,這部分內容也是讓我深有感觸的。第
5
章是本書所講的主體真正開始的序幕,介紹了一份統領全書的重構名錄,起到了提綱挈領的作用;然后第
6
章介紹了第一類重構方法:重新組織你的函數(
Compsosing Methods
),很多方法正是我平常使用的——使用時并不知道這樣做就是重構,也不知道有這些名稱——不過,這些方法都來自于實踐,因此會產生這些共鳴也是理所當然的。但是一些壞味道我也不完全理解,在編程時沒有遇到過,有些重構手法在我看來也并沒有太大必要,這些可能有待日后編程經驗的進一步增加才能更好的理解吧。
2.1
代碼的壞味道
???
用味道來形容代碼質量,是一種很屌也很流行的說法,原來的一些叫法都比較土,總之先前也看過很多被稱為好的編程風格的文章,比如討論一個函數到底最長有多長才算合理之類的。這本書里也包含這些內容,不過比我以前看過的要多。下面只挑一些我遇到過的,并且有些話要說的
Bad Smell
來討論之。
2.1.1
Duplicated Code
(重復代碼)
重復代碼被
Fowler
作為第一種壞味道提出來,或許因為它是目前最常見的問題,同時也是這個人人宣傳要“代碼復用”“最大程度的重用”的時代的主旋律所不許可的。重復代碼在實際的項目中非常害人,主要有以下幾個缺點:
u??????
同樣的代碼,一旦修改,需要多處維護,非常麻煩,還容易出錯。在做那個人力資源系統時我就飽受此苦,修改別人的代碼,不知道那是哪位仁兄編的,同樣的代碼出現了好幾次,搞得我改了這段改那段,相當麻煩。還有前一陣在地磅系統中加入視頻監控功能,在那個擁有
6
個
if
語句的函數里,每個分支語句下都在干一件事,向數據庫的
4
個表中插入數據,
不同的是插入的數據略有區別,但表是一樣的。我需要把我寫的函數加入到這六個分支語句里,而且必須要找準插入點,每個分支語句下的代碼都有五、六十行,當時就在想,這家伙當初編程時怎么不把插入數據庫的代碼寫成一個函數呢?(當然用
DAO
更好了)這些項目都是用
Delphi
完成的,而且整個代碼量奇大無比,領域知識及其復雜,先前的程序員還不按套路出牌,想把重復的代碼重構成一個函數(
Extract Method
,提煉函數)都不敢,因為不知道會產生什么可怕的后果。美國有句土話:如果沒壞就接著用。不過,這樣的隱患隨著代碼量的持續增加,會越來越難纏——中國的很多所謂架構師都不重視代碼質量,殊不知最后我們交付的最有價值的是可以工作的軟件,如果陷入壞味道代碼的泥潭,最后想脫身都難,很容易導致項目失敗(關于這個話題,在《代碼大全》里有很精辟的闡述)。
u??????
編寫重復的代碼會浪費時間,降低生產力。有人可能覺得沒什么,因為重復的代碼都是復制粘貼產生的,但是在實際的開發中,我們大都有這樣的經歷(特別是做沒什么太高難的技術含量的企業信息系統的開發),往往編寫新的功能只需要幾分鐘(但是功能不能太大,反正我是這樣,當設計做好了之后,基本上都是在生產代碼了。特別是熟練之后,不需要查手冊幫助之類的,真的很快),而大量的時間都是在調試了,編寫重復的代碼,意味著每一段重復的代碼在添加同樣的新代碼后都需要調試——很奇怪,這常常會出錯,特別是工作環境很惡劣的時候。
2.1.2
Long Method
(過長函數)
????
剛才我所說的那個包含
6
個
if
語句的函數有
10
來屏(使用
Delphi 7
,
17
寸純平顯示器),真是痛苦的不行,往往看了后面忘了前面,估計當時寫這個函數的人可能很爽,想到什么寫什么,這可苦了我們后面這些人,理解這個函數花了我
6
個小時——這豈不是在大大降低生產率嗎?以前看李開復寫過的一篇文章,說微軟以前有個大牛寫了個
1000
多行的函數,而且這個函數還難度極高,沒有一句重復一句廢話,并且改動一句整個系統就不能用了,搞得后來沒人敢改這個函數。好像后來有人把這個函數重寫了,分成了很多小函數。
????
一個函數多長算長?很多人說最長不要超過一屏,我覺得這個標準是個可視化的標準,可以量化,不過有些死板。我覺得這要視實際情況而定,函數不能太長,同時也不能過于瑣碎,分得塊太小也不太便于理解,我想還是根據領域模型來考慮函數長度吧,每個函數只干自己的事,不要越俎代庖,反正從我的經驗來看,按照這個原則就不會編寫出特別長的函數,特別是使用
Java
這種優雅的面向對象語言。一件事情有
30
個步驟怎么也做完了吧,加上那些聲明、創建的代碼,五六十行應該都能搞定了。五六十行超過一屏了,不過也不會太糟糕,因為核心算法的代碼都在一屏里,不會有什么太大的影響。
?
2.1.3
Large Class
(過大類)
其實與
Long Method
一樣,一個類封裝了太多的方法,但是我遇到這種情況都是發生在沒有正確運用
OO
,甚至根本不知道
OO
的時候,這很有問題,雖然很多人使用
Java
編程,但是并不意味他們是運用
OO
的思想在進行程序設計,我經常看到這樣的
Javabean
,里面包含了一個模塊所有方法——我通常把它叫做
Method Container
,認真學習
OO
設計,應該不會出現如此龐大的類(包含在
class
中的未必是類,類要有意義,否則就是函數集,不能稱為類)。
2.1.4
Long Parameter List
(過長參數列)
讓我印象最深刻的一次遇到這個問題是做視頻監控系統時,起初只是為構造函數添加參數,結果參數越來越多,竟然有
7
個,后來發現很多參數其實是一個類的屬性,當時很后悔起初要是按照
OO
設計就不會出現這樣的情況了,最初圖省事,結果后來反倒麻煩,不得不重構,采用
Preserve Whole Object
(
288
)搞定了。
?
2.1.5
Switch Statements
(
switch
驚悚現身)
前面提到的那個視頻系統出現
6
個
if
語句,其實這
6
個
if
語句是包含在
switch
分支中的一個
case
里的。這個
switch
比那
6
個
if
語句還要恐怖,它不僅意味著重復代碼,因為兩個分支在執行類似的代碼,只是變量賦給的值不同,還意味著魔法數字的出現。由分支條件控制,整個程序在這兩段中來回蹦(謝天謝地只有兩個分支)。
?
2.1.6
Lazy Class
(冗贅類)
?
有些因為經過不斷重構而逐步失去自身價值的類——只剩下一個空殼了——就讓它消失吧。這種類在項目中實在是讓后續的開發人員浪費大量寶貴時間。其實這一點推而廣之就是程序中任何沒什么用處的代碼立馬刪掉,然而現實的某些想法常常讓人放棄這么做,最可怕的是在最開始這種現象初露端倪時姑息縱容,到后來就變得愈發不可收拾了。這種現實想法往往是舊的代碼先不要刪,這樣如果增加的代碼不能更好的工作可以退回去,然而新的代碼可以正常工作后,又懶得去管那些廢棄的代碼了,于是就留下了很多遺留代碼。由于大型項目往往都不會是一伙人做完,跨越六七年的項目中途換將也是有情可原,即使不會換,但是維護和開發也有可能是兩撥人,這樣后繼者也不知道哪些代碼是有用的哪些是廢棄的,很多時間浪費在了讀根本沒有價值的廢代碼上了,而且大家還不敢刪除,怕引起麻煩,這樣廢代碼越來越多,最后有價值的代碼被廢代碼淹沒了,項目也就死了。
2.1.7
Speculative Generality
(夸夸其談未來性)
這種現象也特別普遍,很多人都在為莫須有的功能努力,記得網上流傳的真正的程序員的特點有一條叫真正的程序員認為自己比用戶更清楚用戶想要什么。在某些情況下確實是這樣,因為畢竟我們可能做過許多類的項目,發現了客戶都是在軟件不斷的迭代中學習逐步了解自己需要什么,我們就可能產生某種預見性,估計到客戶也可能像過去那些同類客戶一樣,在未來產生這樣的需求。不過這個事情就不好說了,因為我也碰到過我們替用戶想需求,結果并不是客戶想要的,我們費了好大勁實現的功能客戶一點都不看好,只好廢棄重寫。我們那時候做人力資源系統中的工資模板管理模塊時就遇到了這種情況。
2.1.8
Data Class
(純稚的數據類)
?
這個就是被稱為啞對象的類,
Rod Johnson
也曾在其著作中批評過
TO
這樣的啞對象。這種類只有屬性和存取器方法,沒有任何其他的方法。我對
Data Class
沒有那么極端的鮮明的好惡,因為那時候在用
JSP+Servlet+Javabean
模仿
MVC
框架和
O/R Mapping
框架時使用
VO
、
PO
這樣的
Data Class
,覺得還是挺有作用的,它可以使得顯示層更加的單純,將邏輯封裝在業務門面中,然后將顯示結果封裝在
VO
里,然后直接與
JSP
頁面形成對應關系;數據訪問也同理,感覺很好的解決了一些問題。后來看
Struts
、
Hibernate
時,發現它們借助
IDE
或其他工具把這種
Data Class
都自動生成了,覺得挺好的,因為那些類實在沒什么必要一個一個編寫。
2.1.9
Comments
(過多的注釋)
Fowler
指正注釋過多是一種壞味道主要源于現實——糟糕的代碼試圖用長長的注釋來彌補,是不對的。然而在我遇到的情況往往是注釋亂寫,有的沒必要注釋的地方寫了很多注釋,反而很難理解的算法、流程反而沒有注釋了。有很多方法如
getUserByID
(
int id
),本來很明確了,非要寫注釋;而一套復雜的商業規則,竟然一行注釋都沒有,僅有的注釋就是變量名(如果變量名如果起的能自名義,根本就不需要注釋)。這種現象的出現主要是作者本身也沒有特別清晰的思路,并沒有理順好算法或規則,所以寫不出清晰的注釋——但這種注釋很重要,要不鬼都看不懂,特別是出現魔法數字時(當然盡量避免出現這種情況)。
2.1.10
其他
一共有
22
種壞味道在這本書里,但是我只舉出
9
個,因為這
9
個屬于比較淺層次的,還有一些關于父類子類繼承關系的壞味道,因為我沒怎么用過這種繼承,周圍人也很幾乎沒用過,所以就沒遇過這種壞味道了。上面寫過的壞味道其實只是借助它們的名字說一些問題,這些問題造成了程序質量的低下,難以維護,難以擴展,容易出錯,看也看不懂,改也很費事。我想一個有經驗的程序員看了這
22
種壞味道(當然在實際項目開發中還有很多壞味道書里沒有提及),應該就會有自己的一些解決方案了,很多方法跟
Fowler
提供的方法是一樣的,這一點
Fowler
在書的前面也提到了。但是建議大家看看這本書,
Fowler
已經提出了每種壞味道的解決方案,如果已經看完了所有的重構方法,也可以看這部分來復習所學的主要的重構方法。
2.2
什么是重構
???????
按照
Fowler
的說法,重構有兩個定義,一個是其作為名詞的定義,一個是其作為動詞的定義。
重構(名詞):對軟件內部結構的一種調整,目的是在不改變
[
軟件之可察行為
]
前提下,提高其可理解性,降低其修改成本。
重構(動詞):使用一系列重構準則(手法),在不改變
[
軟件之可察行為
]
前提下,調整其結構。
相關的細節我就不解釋了,因為很簡單。
2.3
構筑測試體系
???
這一點也是我這次去廣州做項目體會到的,起初也沒寫測試程序,重構時擔驚受怕的,后來針對一些方法寫了測試程序,確實大大提高了效率,以后再作開發一定要寫測試,雖然表面上費時,但實際上可以省去很多反復調試的時間,那次是用
Delphi 7
,也沒什么單元測試工具,自己寫測試比較麻煩,而在
Java
中有了
JUnit
,在
Delphi 2005
中也有了
DUnit
,應該好好珍惜,測試驅動是絕對有好處的。而且重構不寫測試,很容易改錯,我就是在修改程序時有一個對象原先需要創建,后來不需要了,我只刪掉了它的對象銷毀方法,卻忘了刪掉創建代碼,結果花了一天時間來找錯,后來實在不行了,編寫測試,才搞定了,天大的教訓啊。
2.4
重新組織你的函數
在這一部分我覺得最有價值的是
Extract Method
(提煉函數),其他的倒是沒太發現太大的用處,可能是因為我沒遇到過這種情況吧。后面的有些方法我覺得有點過于純化代碼了,應該適度采取,在復雜的環境下,不排除它們是好的方法。(畢竟
Fowler
比我的經驗多得多,見過的項目和程序也多得多,以后可能會在某個時候用到它們)
2.5
其他
像《重構》這樣的書,和《設計模式》一樣,都不是看了一遍就可以束之高閣的,在實踐中遇到困難回過頭來看,慢慢才會真正提高。
另外這本書本來熊節翻譯就好了嘛,我看他參與翻譯的《
J2EE
核心模式》不是很好嘛,干嘛非要拉上侯捷,最煩看他翻譯的書,不使用大陸通用的術語翻譯方法,還總是用些不明不白含糊其辭的文言,看了真是不爽。我還買了本英文版的,等回去對照那本一起看吧——另外一定得好好學英語了,要不它會成為我發展的瓶頸的。
3
、設計模式
????
只是將這些模式總結到了一個表里了,設計模式更是沒有編碼經驗就看不懂的東西,以前變成時還用過工廠方法,確實解決了不少問題。而且我越來越發現,很多高層次的書都離開不設計模式。比如那時候看《
Ajax in action
》,又有多少新東西呢,到后面就都是設計模式,工具的介紹,實際案例等等內容了,那時看過一篇文章說高手看書快,因為每本書的新東西都很有限,其實一點都不假,因為我已經看過《
Ajax
基礎教程》了,再加上《設計模式》,《
J2EE
核心模式》,一些工具(無非就是測試工具、調試工具、建模工具等),一些第三方類庫(框架),再加上點項目介紹,如果對這些部分多很了解,那么給它們套上一層
Ajax
的罩衣,不是很容易嗎?
Ajax
,如果熟悉
Javascript
,熟悉
DOM
,熟悉
XML
,熟悉某種服務器端語言,熟悉
ActiveX
等模型,只需要學習一下
XMLHttp
對象不就
OK
了嗎?根本不需要太長時間,也沒有任何難度。然后還可能更進一步了解一些諸如企業級服務(遠程調用,事務處理等),管理方面的知識,不過如此嘛。當然這只是研究的一個方向,這里面一定存在很多最佳實踐,但是仔細研究那些最佳實踐甚至是模式,也只有一小部分是真正的創新,很少很少。其實學習
SOA
技術也不過如此,學學分析模式,分布式系統架構的知識,
XML
、
J2EE
發展史等等,
SOA
也不是什么新的會讓
35
歲以后的人學不會的東西。這個話題激發了我想寫一篇怎么才不會
35
歲之后因為跟不上而被淘汰的文章了,好了,就寫這么多了。
?
|
目的
|
?
|
創建型
|
結構型
|
行為型
|
?
|
范圍
|
類
|
將對象的部分創建工作延遲到子類
|
使用繼承機制組合類
|
使用繼承描述算法和控制流
|
|
Adapter
(類)
|
|
Factory Method
|
Interpreter
|
|
定義一個用于創建對象的接口,讓子類決定將哪一個類實例化,
Factory Method
是一個類的實例化延遲到子類
|
將一個類的接口轉換成客戶希望的另一個接口。
Adapter
模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。
|
給定一個語言,定義它的文法的一種表示,并定義一個解釋器,該解釋器使用該表示來解釋語言中的句子。
|
|
Template Method
|
|
定一個操作中的算法的骨架,而將一些步驟延遲到子類中。
Template Method
使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。
|
|
?
|
對象
|
將對象的部分創建工作延遲到另一個對象中
|
描述對象的組裝方式
|
描述一組對象怎樣協作完成單個對象所無法完成的任務
|
|
Adapter
(
對象
)
|
|
Chain of Responsibility
|
|
Abstract Factory
|
為解除請求的發送者和接收者之間耦合,而使多個對象都有機會處理這個請求。將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個對象處理它。
|
|
Command
|
|
?
|
將一個請求封裝成為一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日志,以及支持可取消的操作。
|
|
提供一個創建一系列相關或相互依賴對象的接口,而無須指定它們具體的類
|
Iterator
|
|
Bridge
|
|
提供一種方法順序訪問一個聚合對象中各個元素,而又不需要暴露該對象的內部表示
|
|
Mediator
|
|
Builder
|
將抽象部分與它的實現部分分離,使他們都可以獨立的變化
|
用一個中介對象來封裝一系列對象交互。中介者使各對象不需要顯示的相互引用,從而使其耦合松散,而且可以獨立的改變它們之間的交互。
|
|
Memento
|
|
將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示
|
在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態。這樣以后就可以將該對象恢復到保存的狀態。
|
|
Composite
|
|
將對象組合成樹形結構以表示“部分
-
整體”的層次結構。
Composite
使得客戶對單個對象和復合對象的使用具有一致性。
|
|
Prototype
|
Observer
|
|
定義對象間的一種一對多的依賴關系,以便當一個對象的狀態發生改變時,所有依賴它的對象都得到通知并自動刷新。
|
|
用原型實例指定創建對象的種類,并且通過拷貝這個原型來創建新的對象
|
|
Singleton
|
|
State
|
|
允許一個對象在其內部狀態改變時改變它的行為。對象看起來似乎修改了它所屬的類。
|
|
保證一個類僅有一個實例,并提供一個訪問它的全局訪問點
|
|
Decorator
|
|
動態地給一個對象添加一些額外的職責。就擴展功能而言,
Decorator
模式比生成子類方式更為靈活
|
Strategy
|
|
定義一系列算法,把它們一個個封裝起來,并且使它們可以互相替換。本模式使得算法的變化可獨立于使用它的客戶。
|
|
Facade
|
Visitor
|
|
表示一個作用于某對象結構中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用于這
?
些元素的新操作。
|
|
為子系統中的一組接口提供一個一致的界面,
Fa?ade
模式定義了一個高層接口,這個接口使得這一子系統更加容易使用
|
|
Flyweight
|
|
?
|
運用共享技術有效的支持大量細顆粒度的對象
|
|
Proxy
|
|
為其他對象提供一個代理以控制對這個對象的訪問
|
|
?