重構-改善既有代碼的設計
--摘錄
1.1 何謂重構
重構不只是整理代碼,它提供了一種更高效且受控的代碼整理技術。運用重構技術後,我發現自己對代碼的整理比以前更有效率。
重構的目的是使軟件更容易被理解和修改。
使用重構技術開發軟件時,你把自己的趕時間分配給兩種不同的行為:添加新功能,以及重構(兩頂帽子)。添加新功能時,你不應該修改既有代碼,只管添加新功能。重構時,你就不能再添加功能,只管改進程序結構。
軟件開發過程中,你可能會發現自己經常變換帽子。首先你會嘗試添加新功能,然後會意識到:如果把程序結構改一下,功能的添加會容易得多。於是你換一頂帽子,做一會兒重構工作。程序結構調整好後,你又換上原先的帽子,繼續添加新功能。新功能正常工作
後,你又發現自己的編碼造成程序難以理解,於是又換上重構帽子……整個過程或許只花十分鐘,但無論何時你都應該清楚自己戴的是哪一頂帽子。
1.2 為何重構
a.重構改進軟件設計
如果沒有重構,程序的設計會逐漸腐敗變質。當人們只為短期目的,或是在完全理解整體設計之前,就貿然修改代碼,程序將逐漸失去自己的結構,程序員愈來愈難通過閱讀源碼而理解原來的設計。重構很像在整理代碼,你所做的就是讓所有東西回到應處的位置上
。代碼結構的流失是累積性的。愈難看出代碼所代表的設計意圖,就愈難保護其中設計,於是該設計就腐敗得愈快。經常性的重構可以幫助代碼自己有的形態。
完成同樣一件事,設計不良的程序往往需要更多代碼,這常常是因為代碼在不同的地方使用完全相同的語句做同樣的事。因此改進設計的一個重要方向就是消除重復代碼。這個動作的重要性在於方便未來的修改。代碼量減少並不會使系統運行更快,因為這對程序的
運行軌跡幾乎沒有任何明顯影響。然而代碼量減少將使未來可能的程序修改動作容易得多。代碼愈多,正確的修改就愈困難,因為有更多代碼需要理解。你在這兒做了點修改,系統卻不如預期那樣工作,是因為你沒有修改另一處——那兒的代碼做著幾乎完全一樣的事情
,只是所處環境略有不同。如果消除重復代碼,你就可以確定所有事物和行為在代碼中只表述一次,這正是優秀設計的根本。
b.重構使軟件更容易理解
所謂程序設計,很大程度上就是與計算機交談:你編寫代碼告訴計算機做什麼事,它的響應則是精確按照你的指示行動。你得及時填補“想要它做什麼”和“告訴它做什麼”之間的縫隙。這種編程模式的核心就是“准確說出我所要的”。除了計算機外,你的源碼還
有其他讀者:幾個月後可能會有另一位程序員嘗試讀懂你的代碼並做一些修改。我們很容易忘記這第二位讀者,但他才是最重要的。計算機是否多花了幾個小時來編譯,又有什麼關系呢?如果一個程序員花費一周時間來修改某段代碼,那才要命呢——如果他理解了你的
代碼,這個修改原本只需一小時。
問題在於,當你努力讓程序運轉的時候,不會想到未來出現的那個開發者。是的,我們應該改變一下開發節奏,對代碼做適當修改,讓代碼變得更易理解。重構可以幫助我們讓代碼更易讀。一開始進行重構時,你的代碼可以正常運行,但結構不夠理想。在重構上花
一點點時間,就可以讓代碼更好地表達自己的用途。這種編程模式的核心就是“准確說出我所要的”。
關於這一點,我沒必要表現得如此無私。很多時候那個未來的開發者就是我自己。此時重構就顯得尤其重要了。我是個很懶惰的程序員,我的懶惰表現形式之一就是:總是記不住自己寫過的代碼。事實上,對於任何能夠立刻查閱的東西,我都故意不去記它,因為我
怕把自己的腦袋塞爆。我總是盡量把該記住的東西寫進程序時,這樣我就不必記住它了。
這種可理解性還有另一方面的作用。我利用重構來協助我理解不熟悉的代碼。每當看到不熟悉的代碼,我必須試著理解其用途。我會真正動手修改代碼,讓它更好地反映出我的理解,然後重新執行,看它是否仍然正常動作,以此檢驗我的理解是否正確。
一開始我所做的重構都像這樣停留在細枝末節上。隨著代碼漸趨簡潔,我發現自己可以看到一些以前看不到的設計層面的東西。研究代碼時我發現,重構把我帶到更高的理解層次上。
c.重構幫助找到bug
Kent Beck經常形容自己的一句話:“我不是個偉大的程序員,我只是個有著一些優秀習慣的好程序員。”重構能夠幫助我更有效地寫出強健的代碼。
d.重構提高編程速度
終於,前面的一切都歸結到了這最後一點:重構幫助你更快速地開發程序。
聽起來有點違反直覺。當我談到重構,人們很容易看出它能夠提高質量。改善設計、提升可讀性、減少錯誤,這些都是提高質量。但這難道不會降低開發速度嗎?
我絕對相信:良好的設計是快速開發的根本——事實上,擁有良好設計才可能做到快速開發。如果沒有良好設計,或許某一段時間內你的進展迅速,但惡劣的設計很快就讓你的速度慢下來。你會把時間花在調試上面,無法添加新功能。修改愈來愈長,因為你必須花
愈來愈多的時間去理解系統、尋找重復代碼。隨著你給最初程序打上一個又一個的補丁,新特性需要更多代碼才能實現。真是個惡性循環。
良好設計是維持軟件開發速度的根本。重構可以幫助你更快速地開發軟件,因為它阻止系統腐敗變質,它甚至還可以提高設計質量。
1.3 何時重構
當我談論重構時,常常有人問我應該怎樣安排重構時間表。我們是不是應該每兩個月就專門安排兩個星期來進行重構呢?
幾乎任何情況下我都反對專門撥出時間進行重構。在我看來,重構本來就不是一件應該特別撥出時間做的事情,重構應該隨時隨地進行。你不應該為重構而重構,你之所以重構,是因為你想做別的什麼事,而重構可以幫助你把那些事做好。
三次法則
Don Roberts給了我一條准則:第一次做某件事時只管去做;第二次做類似的事會產生反感,但無論如何還是可以去做;第三次再做類似的事,你就應該重構。即事不過三,三則重構。
a.添加功能時重構
最常見的重構時機就是我想給軟件添加新特性的時候。此時,重構的直接原因往往是為了幫助我理解需要修改的代碼——這些代碼可能是別人寫的,也可能是我自己寫的。無論何時,只要我想理解代碼所做的事,我就會問自己:是否能對這段代碼進行重構,使我能
更快地理解它。然後我就會重構。之所以這麼做,部分原因是為了讓我下次再看這段代碼時容易理解,但最主要的原因是:如果在前進過程中把代碼結構理清,我就可以從中理解更多東西。
在這裡,重構的另一個原動力是:代碼的設計無法幫助我輕松添加我所需要的特性。我看著設計,然後對自己說:“如果用某種方式來設計,添加特性會簡單得多。”這種情況下我不會因為自己過去的錯誤而懊惱——我用重構來彌補它。之所以這麼做,部分原因是
為了讓未來增加新特性時能夠更輕松一些,但最主要的原因還是:我發現這是最快捷的途徑。重構是一個快速流暢的過程,一旦完成重構,新特性的添加就會更快速、更流暢。
b.修補錯誤時重構
調試過程中運用重構,多半是為了讓代碼更具可讀性。當我看著代碼並努力理解它的時候,我用重構幫助加深自己的理解。我發現以這處程序來處理代碼,常常能夠幫助我找出bug。你可以這麼想:如果收到一份錯誤報告,這就是需要重構的信號,因為顯然代碼還不
夠清晰——沒有清晰到讓你能一眼看出bug。
c.復審代碼時重構
最好是一個復審者搭配一個原作者,共同處理這些代碼。復審者提出修改建議,然後兩人共同判斷這些修改是否能夠通過重構輕松實現。
為什麼重構有用?
對於今天的工作,我了解得很充分;對於明天的工作,我了解得不夠充分。但如果我純粹只是為今天工作,明天我將完全無法工作。
重構是一條擺脫困境的道路。如果你發現昨天的決定已經不適合今天的情況,放心改變這個決定就是,然後你就可以完成今天的工作了。明天,回頭看今天的理解也許覺得很幼稚,那時你還可以改變你的理解。
是什麼讓程序如此難以相與?眼下我能想起下述四個原因,它們是:
1)難以閱讀的程序,難以修改;
2)邏輯重復的程序,難以修改;
3)添加新行為時需要修改已有代碼的程序,難以修改;
4)帶復雜條件邏輯的程序,難以修改。
因此,我們希望程序:(1)容易閱讀;(2)所有邏輯都只在唯一地點指定;(3)新的改動不會危及現有行為;(4)盡可能簡單表達條件邏輯。
重構是這樣一個過程:它在一個目前可運行的程序上進行,在不改變程序行為的前提下使其具備上述美好性質,使我們能夠保持高速開發,從而增加程序的價值。
1.4 何時不該重構
重寫(而非重構)的一個清楚訊號就是:現有代碼根本不能正常動作。你可能只是試著做點測試,然後就發現代碼中滿是錯誤,根本無法穩定運作。記住,重構之前,代碼必須起碼能夠在大部分情況下正常運作。
另外,如果項目已近最後期限,你也應該避免重構。
重構與設計
重構改變了預先設計的角色。如果沒有重構,你就必須保證預先做出的設計正確無誤,這個壓力太大了。這意味如果將來需要對原始設計做任何修改,代價都將非常高昂。因此你需要把更多時間和精力放在預先設計上,以避免日後修改。
如果你選擇重構,問題的重點就轉變了。你仍然做預先設計,但是不必一定找出正確的解決方案。此刻的你只需要得到一個足夠合理的解決方案就夠了。你很肯定地知道,在實現這個初始解決方案的時候,你對問題的理解也會逐漸加深,你可能會察覺最佳解決方案和你當初設想的有些不同。只要有重構這把利器在手,就不成問題,因為重構讓日後的修改成本不再高昂。
重構可以帶來更簡單的設計,同時又不損失靈活性,這也降低了設計過程的難度,減輕了設計壓力。一旦對重構帶來的簡單性有更多感受,你甚至可以不必再預先思考前述所謂的靈活方案——一旦需要它,你總有足夠的信心去重構。