一、前言
我最近做的主要工作是重構我們系統的核心功能。做過代碼重構的同學都知道,代碼重構很多時候是個費力不討好的事情。因為代碼重構是在不修改軟件功能的情況下,對軟件內部進行調整優化。
不修改功能便意味著從業務角度講,這項工作不會帶來什么收益。因為這一點,代碼重構工作通常難以得到充分的資源投入。而且,代碼重構工作不同于普通的代碼開發,代碼重構往往需要投入核心的高水平的研發人員,所以會占用更多的研發資源。
另一方面,代碼重構不僅不會給業務帶來收益,一不小心往往還會帶來很大的風險,尤其是對關鍵的代碼、核心的代碼重構。因此,大領導出于業務和研發資源等方面的考慮,通常對代碼重構都持有非常謹慎的保留態度。
但從研發工作角度講,代碼重構往往是必要的。具體到我所在的項目,最近做過調查,團隊中已有近90%的研發同學表示自己項目中的代碼有明顯的難以理解、難以修改的問題。更有同學表示因為經常接觸爛代碼,自己以忍無可忍,每天的工作就是打補丁,毫無成就感可言。
另外,通過代碼檢查工具,發現代碼在圈復雜度、重復率方面有嚴重的問題,具體的數據就不展示。所以,從直觀感受和量化指標上,都說明我們的代碼急需重構。
所以,從研發工作角度看,代碼重構的必要性往往是存在的。而從業務層面看,領導往往是不喜歡(至少是不主動支持)代碼重構的。
那如何協調這個矛盾呢?
其實代碼重構的實施主要存在兩大問題:一是研發資源的問題;二是重構質量的問題。第一個問題不在本文的范圍內,談談第二個問題。質量問題說白了就是代碼重構之后功能還是否正確。如果代碼重構只能功能不正常了,那代碼再漂亮也沒用。如果能保證代碼重構之后不出錯,那領導自然就會少很多顧慮,代碼重構工作也更容易實行。
接下來就談談如何更好地測試重構之后的代碼。
二、測試方案
任何軟件開發都離不開測試,代碼重構更是如此,而且代碼重構往往會帶來更多的回歸測試工作量。假如有充分的測試用例和完善的自動化測試保證,那大量的回歸測試往往不是難題。但開發、產品、測試換了一茬又一茬,早期的需求已被埋入塵土;早期工作方式簡單粗獷,代碼都沒寫好,有何談自動化測試。所以,充分的測試用例和完善的自動化測試很多時候是不存在的。
那怎么辦?在時間緊,任務重的情況下,怎么能在短時間內將這些測試工作準備好?
方法一:對比測試
測試用例,簡單說,就是給定一個場景(輸入),驗證在這個場景下軟件的行為(輸出)。所以定義測試用例就需要定義測試時的輸入輸出和驗證規則。
但在代碼重構中,因為軟件的功能并未增加,因此也就沒有增加新的測試用例。并且,重構前的老系統本身就提供了大量的測試用例。為什么這么說,因為重構之后的新系統的各項業務行為只要和老系統保持一致,那就可以說是正確的。
所以,對于代碼重構工作,測試用例的編寫就被大大簡化:只需定義輸入,無需定義輸出,將老系統的輸出作為輸出即可。同時,驗證規則也簡化了很多,各項數據通常只需和老系統保持一致,少數的不能完全一致的數據,只需驗證其滿足一定規則即可。
具體方法:
定義測試用例輸入,分別調用新(重構后的系統)、老系統。用老系統的結果校驗新系統,從而降低測試的工作量,提高測試效率。
方法二:導流測試
方法一可以幫助簡化對測試結果的驗證,但是測試輸入還是要想辦法豐富起來,否則漏掉一個場景就有可能放過一個 bug。
對于老功能,需求早已丟失,當年設計開發它的人也已不在,那如何為這樣的確實需求的功能設計測試用例。有一個方法是直接使用線上的請求測試。
具體方法:
通過使用 Nginx 的 ngx_http_mirror_module 模塊(或其它技術,如 tcpcopy),復制一份線上的請求,然后將這份復制的真實請求導向部署了重構版本應用的服務器。然后再通過方法一介紹的對比測試方式,比較線上應用和重構后應用的輸出結果(返回值、數據庫記錄等等),從而驗證代碼重構的正確與否。
但是這種測試方法有一定的局限性。簡單來說,這種方式適合測試讀接口,不太適合或者說是難以測試寫接口。因為測試請求來自線上,如果被測服務器同樣部署在線上環境,那寫接口就會對用戶造成應用。如果被測服務器部署在測試環境,需要在測試環境完整同步一份線上數據,這需要相當的基礎測試設施的支持。
三、總結
上面介紹了在面對代碼重構時的一些測試方法,希望能幫助大家安全順利的上線代碼重構,讓項目的代碼質量越來越好。
上面介紹的方法解決的問題范圍是有限的,我工作接觸到的問題也有限。所以歡迎大家能針對這個問題提出自己的意見和方案。