最近重構了公司一個將近10年的核心功能模組,踩了不少坑。在做這個重構的時候好幾次都覺得做不下去,好幾次壓力都非常大,心想著我該不會做著做著就退出程式設計屆了吧。
不過還好,自己還是堅持下來了,回想寫這個專案的時候自己曾三次推翻重來,那種心路歷程真的只有經歷了才知道,真是煎熬。後來回想起這一路踩過的坑,其實更多的是經驗問題,而不是技術方面的問題。
關於心態
回顧做這個專案,我覺得心態問題是最重要的,技術問題倒是其次。為什麼這麼說呢?因為對於10餘年的老功能模組來說,其中最複雜的其實是業務邏輯,而並非技術實現。所以對於老系統的重構,你首先需要將這十餘年來積澱在該模組的業務邏輯梳理清楚,這本身就給了重構者一個無形的壓力。再加上又是核心業務模組,少一點業務邏輯就會導致線上收入的減少,最後就是程式設計師祭天的悲慘命運。這一系列的背景,使得重構之時心理壓力真的很大。
現在回頭來看,對於重構專案,最好的方式還是先仔仔細細梳理清楚所有的業務邏輯,之後將其用思維導圖畫出來,這樣你會對這十幾年來的業務邏輯一清二楚。清楚了業務邏輯,對於你後面進行系統重新設計以及編碼都是大有裨益的,甚至是起決定性作用的一環。
說到心態這個話題,無論是做什麼專案,即使是重構,也會涉及到排期問題。有時候很可能你自己還沒完全瞭解業務邏輯之時,上級就要你給出一個排期,這時候其實作為重構者是很為難的。對於這種情況,其實我建議還是與上級做好充分的溝通,如果能延長給排期的時間是最好的。如果不能,那應該主動上報每天的進度,讓上級知道你的進展。其實要你給出排期,不就是為了掌控進度嗎。
當你給出排期後,很可能會出現了許多當時預估排期時沒有出現的情況,這時候一般我們有兩種選擇:一種是急急忙忙草草處理了事,另一種則是穩定心態思考最好的解決方式。其實當你發覺另一種做法是更好的處理方式,但這種方式在目前排期下無法完成,這時候作為整體功能的開發重構人,你應該有自己的判斷。即使是因為做了這件事情而延期,但只要你做的這件事情是對的,有利於系統擴充性和穩定性的,那還是應該堅持自己的選擇。我想,如果你因為正確的堅持而延期,我想公司並不會因此而責怪,只要你給出了自己的理由。最怕的是驚慌失措地寫完一個東西,而這東西又漏洞百出。
所以有時候覺得開發或重構一個系統真的是得抱著必死的決心,這樣才能有自己的堅持,而不會在上級的排期壓力之下做出錯誤的選擇。特別對於重構類的專案,如果沒有一個從容的心態,那系統是肯定做不好的。
關於技巧
我覺得重構中的經驗技巧遠重要於技術實力,因為一個經驗可以讓你減少很多不必要的麻煩。在說出我的心得之前,我想問一個問題:
在你重構的時候遇到一個問題,這個問題解決了會更好,但不解決也不會影響到此次專案的結果。請問,此時你是解決這個問題,還是不解決好?
有些人會分析:這要看情況,如果我有時間我會去解決,如果沒有時間,那我就會跳過它。一般會給出這種答案的人,都是理論上的巨人,行動上的矮子,基本可以斷定沒有經歷過實戰。因為其分析很符合馬克思主義的辯證主義思想啊,這也確實沒錯。但這樣的解決方式對於實際情況是不夠有用的。
對於這種情況,我建議是不做。準確地說:對於任何不影響你達成重構目標的事情,能不做就不做。因為你永遠不知道這個坑到底有多深!在你並不知道坑有多深,而此時又有排期(追兵)在後面,這時候最聰明的做法就是不做。等你把必須做的做完了,你再回頭來看看這個問題,如果你可以解決,那就嘗試著解決。但如果此時你發現坑真的很深,你可以果斷返回,這時你其實已經做完了事情,並不會有排期的壓力了。這樣的做法給自己留足了後路,自己可進可退,可攻可守。但如果你一開始就選擇踩這個坑,那麼可能你會讓自己陷入泥潭。
所以建議大家在不清楚的情況下不做,不是叫大家做事懶惰。而是讓大家明白自己的目的是什麼,在資源(時間)有限的情況下把事情做成。
關於技術
技術是放最後的,因為我確實覺得技術在重構中並不是特別重要。至少在我這次重構中,我基本上60%的工作都是因為我的心態或技巧不足導致的重複勞動。我專案中重構涉及到的技術,我只用了不到10%的時間就完成了。回頭想一想,真是覺得好淒涼。
重構中的技術其實更多的是使用設計模式將複雜的業務邏輯用簡潔的程式碼呈現出來。簡單點來說,就是用設計模式承載複雜的業務邏輯,儘可能使寫出的程式碼簡潔。
怎麼樣才是一個好的系統重構呢?其實有一句話說得特別好:對擴充開放,對修改封閉。意思就是說別人只能擴充你的程式碼,而不能修改你的程式碼。很多老系統為什麼會越來越複雜?邏輯越來越多?原因就是他寫的程式碼允許別人進行修改。這麼說可能會很抽象,我們舉個例子說吧。
1 2 3 4 5 6 7 |
if(type == apple){ //deal with apple } else if (type == banana){ //deal with banana } else if (type == ......){ //...... } |
上面這段程式碼模擬的是對於水果剝皮的處理程式。如果是蘋果,那麼是一種撥皮方法;如果是香蕉,則是另一種剝皮方法。如果以後還需要處理其他水果,那麼就會在後面加上很多 if else 語句,最終會讓整個方法變得又臭又長。如果恰好這個水果中的不同品種有不同的剝皮方法,那麼這裡面又會有很多層巢狀。
可以看得出來,上面這樣的程式碼並沒有滿足「對擴充開放,對修改封閉」的原則。每次需要新增一種水果,都可以直接在原來的程式碼上進行修改。久而久之,整個程式碼塊就會變得又臭又長。
如果我們對剝水果皮這件事情做一個抽象,剝蘋果皮是一個具體的實現,剝香蕉皮是一個具體的實現,那麼寫出的程式碼會是這樣的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public interface PeelOff { void peelOff(); } public class ApplePeelOff implement PeelOff{ void peelOff(){ //deal with apple } } public class BananaPeelOff implement PeelOff{ void peelOff(){ //deal with banan } } public class PeelOffFactory{ private Map<String, PeelOff> map = new HashMap(); private init(){ //init all the Class that implements PeelOff interface } } ..... public static void main(){ String type = "apple"; PeelOff peelOff = PeelOffFactory.getPeelOff(type); //get ApplePeelOff Class Instance. peelOff.pealOff(); } |
上面這種實現方式使得別人無法修改我們的程式碼,為什麼?
因為當需要對西瓜剝皮的時候,他會發現他只能新增一個類實現 PeelOff 介面,而無法再原來的程式碼上修改。這樣就實現了「對擴充開放,對修改封閉」的原則。
其實上面這種設計模式是最基本的設計模式:抽象設計模式。對於各種複雜的業務情形,還有其他許多設計模式,例如:代理模式、裝飾模式等等。但各種模式歸根到底還是考察你對業務的理解能力以及抽象能力,如果你對業務足夠理解,你即使不知道某個設計模式,你也會寫著寫著寫出這樣一個設計模式。
總結
此時重構的經歷讓我覺得十分痛苦,但熬過來了就覺得沒什麼了。忽然感嘆道重構還是很有技巧性的,對於技術要求反而沒有那麼高。重構更多考驗的是對業務的深入理解,對抽象思維的進一步運用。如果業務理解深入,有抽象的思維,那設計模式還可以一點點學出來。而如果反過來,則沒有辦法做下去。
重構何其苦,且做且前行。