我常常喜歡把一個系統比喻成一輛車,你需要經常對它做維護和保養,才能保證它的良好運作。如果不這么做,雖然看著能開,但某一天一個嚴重的問題就會導致極其危險的后果。而持續重構就是我們給系統做的保養,這對于保證系統的穩定運行非常關鍵。
我曾主導過不少系統重構工作,從中也得出了一些我所認為的最佳實踐,希望也能給其他程序員朋友們一些參考。
從構建工具開始
在接手去重構一個新的系統時,我常常會發現他們的構建腳本寫得有多糟糕,有的系統甚至根本沒有使用構建工具。更可氣的是,負責系統的開發人員往往并不把它當回事,這就帶來以下一些問題:
- 自動化程度低下:很多本該由構建工具自動完成的工作,如:編譯,測試,部署,都需要人工干預完成,或者由于構建腳本寫得不好,本來可以增量編譯和部署的,變為每次需要全量編譯和部署,修改一行代碼只用了一分鐘,而等待構建卻用了多達5分鐘時間。
- 缺少有效的包依賴與版本管控:構建工具可以幫助我們進行有效的依賴包與版本管理。缺少了它,則很可能造成因不同開發人員引入的第三方包版本不一致所導致的系統問題。
- 缺少自動化測試覆蓋:構建工具能夠幫助我們在每次版本構建時,執行自動化測試,這對版本的交付是一個非常有效的質量保證。
- 不利于團隊構建:開發團隊總是具有一定流動性的,我們經常需要新人加入團隊參與系統的開發。缺乏良好的構建工具,往往迫使每個新人都需要耗費大量的時間去搭建開發環境,這對團隊的構建也是非常不利的。
因此,對于我來說,系統重構的第一步便是引入構建工具或重寫構建腳本:
- 引入構建工具:對于后端Java程序來說,我最常使用的便是Groovy,目前也開始嘗試使用Gradle。而對于前端來說Grunt或更新的Gulp都是不錯的選擇。
- 第三方包依賴與版本管理:通過定義全局屬性文件的方式,定義如系統的版本號,名稱等信息,并在構建腳本中明確定義引入第三方包的依賴關系與使用的版本。使得整個開發團隊都能使用統一且標準的開發環境。
- 實現自動化:通過定義不同的target(注:Groovy中的一個任務稱為target),來實現不同的目標。比如:增量編譯與本地部署,自動化測試,打全量版本包等等。總之,將一切需要手工完成的任務,利用工具去幫你完成。
- 寫入開發手冊:將構建工具的使用,不同target針對的不同應用場景寫入開發手冊,能夠有效地減少新人的學習成本,并使整個團隊開發效率得到提升。
讓自動化測試成為重構的保障
我們重構的目的往往是去解決系統的某些痛點,這些痛點也往往是系統的核心功能,因此,在你直接動代碼之前,需要詳細分析修改可能造成的關聯影響。下面是我在做關鍵功能重構時所采用的步驟:
- 詳細Review該功能的需求
- 針對需求,完善自動化單元測試案例
- 將這部分單元測試的執行引入到每次自動化構建中
在大部分我重構過的項目中,起初自動化單元測試都是缺失的。由于對核心功能的重構,往往涉及到大量代碼的反復修改,因此,通過引入單元測試,可以非常有效地避免因重構造成的關聯影響。而通過重構完善自動化測試,在我看來也是一個很好的重構實踐,它將為我們未來的持續重構打下良好的基礎。
代碼級的持續重構
雖然我們一開始總是能夠確保代碼的質量,但不可否認,我們的代碼會隨著時間的推移變得越來越糟。這其中可能包括:
- 重復的代碼,它們可能存在于同一個類或不同類中
- 不一致或沒有標識性的對象、變量或方法命名
- 過長的代碼段
- 讓人費解的布爾表達式
- 過于復雜的邏輯判斷
- 對象錯誤地暴露其內部狀態
- 遭廢棄但沒有刪除的類或方法
對于上面這些代碼中的壞味道,你應該一有機會就嘗試去重構它。但記住,你不應該操之過急,想著一下在把所有問題都一起解決。你只需要先識別出這些問題,然后分步驟地逐步去解決,而每次重構你需要充分識別可能造成的關聯影響。如果你已經為你的代碼寫了單元測試,那你的重構將會有很好的測試保證。如果沒有,你也可以盡可能找人幫你review修改的代碼,因為不同的人來看你所修改的代碼總是能發現不同的東西。
另外,我們現在使用的IDE也能為我們提供很多幫助,比如找出要重構的方法在哪些地方被調用到,或者要重構的類的層級關系等等。它還能幫助我們自動地去重命名一個變量、方法甚至是類。
基于微服務的重構
最后,讓我們從架構的角度來看看系統的重構。說到架構,時下最流行的一定是“微服務”架構了。在我看來,微服務并非是一個全新的架構方法論,而是對SOA——面向服務架構的一次升級。它的出現源于云計算、容器技術、DevOps等技術以及全新運維理念的不斷成熟。
最近,我正在主導一個遺留系統基于微服務架構的升級工作。在技術層面,雖然業界已經出現了多個支持微服務的架構,我們選擇的是Spring Boot,主要是因為它的背后是Spring團隊強有力的技術支持與維護。我們的重構也很簡單:
- 服務識別
- UI與服務的剝離
- 構建服務
由于采用微服務架構,我們并不會在原有系統上進行重構,而是創建一個新的基于SpringBoot的項目,將原有系統的功能,逐步拆分成一個個服務,添加到新的項目中,然后利用一些開關設置,將原有功能切換到新的基于微服務的系統中,這是與之前系統重構一個很大的區別。
使用微服務框架,可以使開發變得更加簡明,然而,它的難點恰恰在于服務的發現與定義。你系統中的哪些服務應該被獨立出來,形成對外的服務,服務的粒度又應該是怎樣的呢?我在做相關架構時,其實參考了領域驅動設計的思想,先識別出系統所包含的那些領域模型,然后按照領域的劃分與不同的粒度來規劃系統的服務。
重構雖然無法直接創造業務價值,但卻能顯著提高系統的可維護性,所以你在重構上所投入的每一分鐘,都將轉變為未來節省更多的維護時間,這也是為什么我們需要持續重構的原因。