馬丁·福勒(Martin Fowler)談重構:什麼是重構 ——重構——(1) 轉

重構(refactoring)是現代軟件工程的核心理念之一。它更是一種普適的方法論,特別是對於慣於宏大敘事但往往流於空泛的中國式思維,是一個非常有益且行之有效的互補。馬丁·福勒(Martin Fowler)是現代軟件工程的大師之一。

在過去十年中,馬丁·福勒在商業化信息系統開發領域倡導了許多新的軟件開發技術。他在許多領域的工作都爲世人所矚目,包括:面向對象的分析與設計,軟件模式,統一建模語言,敏捷軟件開發(特別是極限編程),以及重構。著名的出版社艾迪森維斯理(AddisonWesley)曾出版過若干本他的著作,如《分析模式》(1996年10月),《重構》(1999年6月,與肯特·貝克合著),《UML精粹》(1999年8月,與肯德爾·斯考特合著),《規劃極限編程》(2000年10月,與肯特·貝克合著),以及即將出版的《企業級應用架構模式》(2002年11月)。

這次採訪分爲六個部分。福勒談到了他對許多話題的看法,包括重構,設計,測試,和極限編程。在第一部分,福勒探討了重構和測試在開發過程中扮演的角色,並描述了重構、設計、以及可靠性之間的相互作用。

比爾:請給出重構的定義。

馬丁:重構就是對代碼本身做出修改,以改善它的內部結構,但又不改變它的外部表現。

比爾:如果重構既不添加新的功能也不消除已有的漏洞,那它的商業目的是什麼?你是怎麼看待重構的?

馬丁:重構改善了設計。而一個良好的設計,其商業目的何在?我認爲,它使你能在未來更容易地對軟件作出改動。

重構實際上是在說,“來吧,讓我們把系統結構重新調整一下,好讓將來的任何改動都更容易些。”其潛臺詞是,如果你不會改動你的系統,那麼也就沒有必要做重構,因爲不會有任何回報。但如果你將要對你的系統作出改動——不管是消除漏洞也好,還是添加新功能也好——那麼,一個好的或更好的系統結構,會使你在做修改時有所受益。

重構與團隊

比爾:一個團隊中,各人有各人的編程習慣。我的一個朋友在一家公司做事,公司進了個新人,那個人對於什麼是好的命名規則有自己的一套想法:他喜歡在成員名字的前面加上“m_”前綴、在詞與詞之間加上下劃線,等等。於是他做了一些簡單的重構,就像你推薦的那樣,但這些重構造成了一些問題。

比如,由於授權許可的限制,公司的一些代碼是保密的,這個新人無權訪問。結果他的重構造成了代碼不一致,我的朋友不得不修補這個問題。而當我的朋友需要修補在多個版本中都存在的漏洞時,問題又來了:重構導致了在不同的版本中命名規則是不一樣的,這無疑給不同版本的漏洞修補工作增添了難度。此外,公司裏像我朋友那樣的舊員工,已經習慣了舊的命名規則。有一次,我的朋友怎麼也找不到一個方法函數,因爲它的名字被改了。諸如此類的問題,都是因爲那個新人所做的重構。

那麼,在一個團隊裏,應該由誰來決定命名規則?又應該由誰來決定何時以及如何做重構?

馬丁:重構並非讓你變態地去重命名一大堆東西,而理由僅僅是你覺得那樣好。重構必須要有收益。假設你在做重命名,那麼你應該查找那些名字和意義不符的方法,並且其他使用該方法的人也覺得應該改名纔行。說到命名規則,一個團隊必須要有一個都能接受的命名規則。如果你是新加入的,那你必須瞭解這套命名規則。假設我作爲一個諮詢師進入團隊,那我不會把我的命名規則強加給團隊。我會詢問團隊的命名規則是什麼,熟悉它們,使用它們。當然,從另一方面說,我堅決反對像“x374”這樣的命名,因爲它不能表達任何意義。

至於說保密的代碼與其他部分代碼不一致的問題,這正說明了他們的測試不是很嚴格。測試對於重構來說,是非常重要的支撐。

重構與測試

比爾:你在《重構》這本書裏寫道:“如果你打算重構,那麼最基本的前提是有完善的測試。”這是不是說,如果沒有測試,就不要重構?

馬丁:沒有測試支撐的重構,就如同不繫安全帶走鋼絲。如果你很擅長走鋼絲,而且鋼絲又不是懸得很高的話,那不妨試試。但如果你從沒走過鋼絲,而鋼絲又是懸在尼亞加拉瀑布上空,那你最好還是有個保靠的安全帶。

比爾:如果你目前沒有任何測試,那麼還有必要追加測試麼?

馬丁:還是那句話,代碼的一致性可能會因爲某人的修改而被破壞,你絕對不希望會有這樣的“驚喜”。而類似JUnit的測試的最大好處就是讓你能通過運行它們看看是否有什麼東西被破壞了。如果你不打算碰你的代碼,那當然平安無事;但只要你加新的功能或是修補漏洞,那麼你就有可能破壞某些東西。你的測試越完善,你對能做的改動就越有信心。最終,你能實現比較高的可靠度。

以測試爲基礎的可靠性是極限編程中不大被人們注意的要點之一。人們在談論極限編程的時候,談得最多的是它的快速反應力以及輕量化的開發過程,但往往不提可靠性。而我聽到越來越多的故事,都表明極限編程有非常高的可靠性。兩週前,我跟以前在 C3 項目的老同事裏奇(RichGarzaniti)聊了聊。克萊斯勒的 C3項目通常被認爲是極限編程的搖籃。在那裏,肯特第一次把各種實踐有機地結合在一起。裏奇談到了他藉助極限編程以及測試和重構所開發的項目。整個系統徹底貫徹了極限編程的精要。今年到目前爲止,他只發現了一處漏洞。

測試與效率

比爾:在《重構》這本書中,你寫道:“作爲一個程序員,我寫測試是爲了提高了自己的工作效率。”那麼,測試是否能提高魯棒性、質量和可靠性呢?

馬丁:會的,這些都是測試帶來的好處。我認爲,程序中的缺陷影響了我們的效率,因爲我們不得不花時間去修正它們。如果我能減少一個缺陷,我的效率就得到了提高。至於說我得到了更魯棒、更可靠的軟件,這都是很有價值的副產品。最基本的一點,還是在於我能節省下跟蹤和修補漏洞的時間,從而編寫出更多的功能。

比爾:也就是說,你花在寫測試上的時間,可以因爲不用修補漏洞而補回來。

馬丁:我能在一天之內就把時間補回來,因爲花在跟蹤調試上的時間大大減少了。花在測試上的成本很快就能收回。隨之而來的還有其它好處。

比爾:假如一段代碼根本沒有測試,那麼寫測試的成本還能很快收回麼?

馬丁:這種情況需要較長的時間。我不建議你花上整整兩個月的時間,就是用來寫測試。但我認爲,通過添加一些測試,你能很快獲得回報,因爲你開始發現問題了。如果你把測試集中在你需要做改動的代碼部分,那麼當你犯錯誤的時候,測試會告知你這些錯誤。顯然,全面綜合的測試會使你受益最大;但是,就算只寫幾個測試,你也會從中受益。

重構與效率

比爾:你說過單元測試可以幫助你提高效率,你還說過重構的好處之一就是能夠更快地編程。那麼,重構是如何幫助你提高編程速度的呢?

馬丁:因爲一個設計良好的程序,修改起來會更容易。程序的設計越好,修改起來就越容易,從而提高了效率。

重構與性能優化

比爾:你在書裏寫道,你之所以做重構,並不是出於好玩,而是因爲“有些與程序有關的事情,如果不做重構,就無法做到;只有做了重構,才能辦到。”那麼,你所指的這些事情,除了對程序進行改動,還有哪些事情呢?

馬丁:對程序進行改動是主要的動因。我們不得不經常對軟件作出一些改動——只要這個軟件還在使用。重構是用來改善設計的。我們需要一個好的設計以使任何改動都更容易些。重構跟性能優化有些類似,都是在行爲不變性(behavior-preserving)前提下的改進。不過,性能優化的步驟有異於重構,整個過程也有所不同,因爲性能優化的驅動要素是性能分析(profiling)。

比爾:也就是說,重構所作的改動,不會增加或改變功能,而是爲了程序更清晰,這樣,將來就可以容易地做出改動。而性能調優的相似之處在於,其所作的改動除了縮短執行時間以外,也不會改變程序的功能。

馬丁:對。它們有相似之處。不過,我一般還是把它們看成是兩個很不一樣的概念。

重構與設計

比爾:您在《重構》一書中寫道:“重構可以幫助我們改進軟件的設計。”那麼,重構是如何改進設計的呢?

馬丁:我無法籠統地回答這個問題。不過你可以分析單個的重構方法,來看看它是如何改進設計的。比如,提取方法(Extract Method)通過把一個很長的、令人費解的方法拆分成一些小方法來改進設計。改進後的方法讀起來就像是一份文檔——一張調用那些小方法的列表。

每種重構方法都會對針對某些特定的設計元素做出改進。應用的時候要具體情況具體分析。很多重構方法都能找到相對立的另一個重構方法。比如,如果一個方法,除了方法本身的代碼所表達的意義之外,沒有任何附加的含義,那麼你可能會內聯它。內聯方法(InlineMethod)與提取方法就是對立的。很多時候,到底應用哪種方法取決於具體情況。

重構與查錯

比爾:你在《重構》一書中還聲稱,重構可以幫助你發現漏洞。重構是如何做到這一點的?

馬丁:我想,重構可以從幾方面幫助你發現漏洞。有時候,當你讓程序變得易懂時,漏洞自然而然地就顯現在你面前。你一邊做着重構,一邊對自己說,“等等,假如給定某種條件,結果會是什麼呢?”我就是這樣發現了很多漏洞,還有很多其他人也都如此。重構使得事情一瞬間清晰起來,因而你能夠一眼就看出漏洞所在。

當你在修補一個漏洞而代碼又晦澀難懂時,重構也能夠幫你發現問題所在。通常,當你在調試一段代碼的時候,對之進行重構也相對容易些。重構後,你就可以輕易地發現漏洞在哪裏。這種清理使得漏洞更容易被暴露出來。

針對包含漏洞的代碼段編寫單元測試,是一種很好的調試技術。其帶來的好處,不僅僅是讓你對代碼的理解更深刻,還讓你建立起測試庫,從而意味着將來不會有問題發生。

重構與重寫

比爾:在《重構》一書中,你列舉了重構可能會遇到的幾個問題,其中有一個是說,在某些情況下不應該做重構。那麼,如何判斷什麼時候應該徹底拋棄現有代碼從頭開始,而不是選擇重構?

馬丁:答案是——我也不知道。如果你有一堆亂七八糟的代碼且又沒有測試,那麼你最好是扔掉它們從頭開始,否則你就得重新做所有的測試。反之,如果你有一堆亂七八糟的代碼同時還有很多測試的話,情況就不一樣了。假如代碼中滿是漏洞,那麼在行爲不變性下,不管怎麼變換,那些漏洞都會被保留下來。這時,是否重構就是一個值得爭論的問題。我想,這個問題的答案也會隨着你對重構熟悉程度的深入而改變。隨着對重構越來越有信心,你可能會對以前想要重寫的一些東西改用重構,因爲你有更強的重構能力了。

比爾:我曾經有過一次經歷,和另外一個諮詢師一起努力使一個有問題的應用運轉起來。我決定把我所負責的那段代碼徹底丟棄,然後從頭開始。而另一位諮詢師試圖重構他所負責的代碼。但結果卻是,他那部分代碼最終也未能穩定運行。最後,我接手了他的工作,把原來的代碼丟掉,從零開始重寫了一遍。這次經歷說明,在某些時刻,如果代碼完全沒有結構,那麼重寫是比重構更有效的一種方法。

馬丁:在決定重寫代碼之前,也許值得花些時間在重構上,來看看能做多少改進。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章