發布時間: 2017-07-04 13:24:50
每個程序員、項目經理或團隊負責人的生命周期中至少會發生一次這樣的事件,你接手一坨超過百萬行代碼的系統,原來的程序員很久以前就離職,現在也許正在某個陽光明媚的地方度假,文檔(如果有的話)最有可能的情況就是與現有的系統不同步。而你的工作則是帶領團隊脫離這個混亂。在經歷逃離的本能回應之后,你開始對項目進行了了解,通過你手頭現有的東西,失敗是大概率發生的事件。但是,公司高層領導是不能容忍項目失敗的結果發生。你該如何應對?
這時,如果能夠把這些垃圾代碼變成健康的可維護的項目,實際上是非常值得一試的事情。以下是我們總結的改進舊版代碼庫的一些真理,希望能對各位有所幫助。
數據備份
我們很難記得每天修改了哪些東西,特別是配置數據容易受到這種問題的影響。配置通常不會進行版本控制,如果能夠進行定期備份,則可以規避很多麻煩。在開始做任何事情之前,你需要備份所有可能相關的內容,放到一個非常安全的地方,確保不管發生什么情況都不會丟失數據。
構建一個真實的仿真環境
構建一個真實的仿真環境是重要的先決條件,第一步是你確保知道現在正在生產環境運行的是什么,這意味著你能夠構建一個軟件版本和你的真實環境保持一致,相同的軟件環境與二進制版本。如果你找不到一個方法來實現這一點,假如你提交代碼到生產環境,就可能會遇到一些令人不快的意外。確保新的代碼在合適環境盡可能地被測試,然后你才會有足夠的信心將其運行到生產環境。上線時做好準備可以隨時切換回老的代碼,并確保通過日志記錄可以了解相關重要內容,以便在后續排查問題時能派上用場。
凍結 DB 修改
盡可能凍結數據庫修改,當完成第一階段的改進,直到團隊對代碼庫有了徹底的了解,遺留代碼已經棄之不用時,才考慮修改數據庫結構。在此之前任何的數據庫修改都可能會導致一些棘手的問題,讓你失去并行運行舊系統和新的代碼庫的能力。保持 DB 完全不變,你可以比較新的業務邏輯代碼與舊的業務邏輯代碼,如果所有這些效果都與預期一樣,則應該完全沒有區別。
編寫測試
在進行任何修改之前,編寫盡可能多的端到端以及集成測試,確保這些測試能夠產生正確的輸出,并覆蓋所有潛在的情況。這些測試將具有兩個重要功能:一方面,幫助技術在早期階段清除任何誤解;另外一方面,一旦你開始編寫新代碼來替換舊代碼,這些測試將可以更好保護您的系統。自動化你的所有測試,如果你已經有 CI 的經驗則盡快使用它,并確保您的測試運行足夠快,以便在每次提交后運行全套測試。
Instrumentation 和日志
如果舊平臺仍然可以增加 Instrumentation,在一個全新的數據庫表中執行此操作,為你可以考慮的每個事件添加一個簡單的計數器,并添加一個單個函數來實現此功能,以根據事件的名稱來增加這些計數器。這樣,你可以使用一些額外的代碼行實現帶有時間戳的事件日志,你將了解到有多少事件導致另一種事件。舉例來說,用戶打開應用程序,用戶關閉應用程序。如果兩個事件導致一些后端請求,那么這兩個計數器應該在長期上保持不變,差異是當前打開的應用程序的數量。如果你看到更多的應用程序打開,而不是應用程序關閉,你知道必須有另一種應用程序結束的方式(例如崩潰)。這個簡單的技巧可以將每個后端應用程序變成一個類似的簿記(bookkeeping)系統,就像一個真正的簿記系統那樣,所有的數字必須匹配,確保它們在所有用到的地方沒有問題。隨著時間的推移,這個系統的健康監控變得非常寶貴,并且將成為源代碼控制系統變更日志的一個很好的伴侶,你可以在其中確定每個錯誤引入的時間點,以及對各種情況產生影響的計數。
每次只修改一個點
在添加新功能或修復錯誤的同時,不要陷入同時改進代碼以及修改代碼運行平臺的陷阱。這會導致很多頭大的問題。
平臺更改
如果你決定將應用程序遷移到另一個平臺,那么請先執行此操作,但要保持一切功能完全一樣。你可以添加更多的文檔或測試,但不能超過這一點,所有業務邏輯和相互依賴關系應該保持原樣。
架構變化
接下來要解決的是改變應用程序的架構。這個時候,你可以隨意更改代碼的較高級別結構,通常通過減少模塊之間的水平鏈接數量,從而減少與最終用戶進行任何交互時代碼活動的范圍。如果舊代碼本質上是一體的,現在將是一個很好的時機使其更加模塊化,將大型功能分解成較小的功能,但是保留變量和數據結構的名稱。架構修改并不總是可行,如果特別不幸運,那么可能需要非常深入理解代碼才能進行任何架構更改。如果你同時進行高級別更改和低級別更改,至少需要將其限制在一個文件,或最壞情況下限制在一個子系統,以便盡可能限制更改的范圍。否則你可能很難調試剛才所做的更改。
低級重構
到目前為止,你應該對每個模塊的功能有很好的了解,并為實際工作做好準備:重構代碼以提高可維護性,并使代碼具備擴展新功能的能力。這很可能是項目中耗時最多的一部分,文檔需要隨之進行,在完整編寫文檔介紹并徹底了解一個模塊之前,不要隨意更改模塊。這個階段也可以修改變量和函數命名、修改數據結構,以提高代碼清晰度和一致性。記得添加相關測試代碼(根據需要,可進行單元測試)。
修復 bug
現在你準備好進行一些最終用戶可見的變化,第一件事情將是修復多年來積累在隊列中的 bug。像往常一樣,首先確認 bug 仍然存在,然后編寫一個測試并修復 bug,你的持續集成和端到端的測試可以幫你避免由于缺乏理解或某些錯誤而導致的任何錯誤及外圍問題。
數據庫升級
如果上述工作都已經完成,你可以再次擁有可靠且可維護的代碼庫,你可以選擇更改數據庫 schema 甚至替換數據庫。已經完成的上述工作都將有助于您以無負擔的方式進行變革,而無需擔心任何意外,你可以使用新的代碼和所有的測試來測試新的數據庫,以確保你的遷移沒有任何問題。
更穩健的重構之路
在路線圖上前行
恭喜,到這里你已經走出了叢林,現在已經準備好可以實施任何新功能了。
不要嘗試徹底重寫
徹底重寫是一種幾乎保證會失敗的項目。
一方面,你是在未知的領域開始,你甚至不知道要重構什么。另一方面,你也將所有的問題推到最后一天,就在你用新系統啟用之前的那一天。很悲劇的是,那也是你失敗的時刻。業務邏輯的假設最終會證實存在問題,那時你將突然了解到為什么舊系統會用某種奇怪的方式來工作,最終也會意識到能將舊系統放在一起工作的人也不都是白癡。如果你真的想要將公司(以及你自己的信譽)帶向一個泥潭,就來一個徹底大重寫吧!但如果你足夠聰明,徹底重寫系統通常不會成為會議桌上的一個討論選項。
替代方案:迭代式改進
要解開這些線團最快的方法就是從你已經理解的代碼入手,并在它的舊的上下文的范圍內嘗試逐步改進。如果舊的構建工具不再可用,你將不得不使用一些技巧,但至少在你開始更改時,盡可能多地保持舊的系統工作。一個典型的提交通常只包含數行代碼。
發布到生產環境
所有修改盡可能發布到生產環境,即使修改的代碼是最終用戶不可見的,因為當你對系統了解不足時,只有生產環境才會告訴你,新的修改哪里會有問題。如果這個問題只是在小的改變之后出現,你將獲得幾個優勢:
合理使用代理服務器
如果你正在重構一個 Web 系統,你可以在最終用戶和舊系統之間部署一個代理服務器。你可以精確控制每個 URL,哪些請求進入舊系統,哪些請求路由到新系統,從而可以更輕松,更精細地控制運行的內容。如果你的代理足夠強大,你甚至可以控制將某個 URL 一定百分比的流量發送到新系統,以便觀察新系統的運行情況。如果你的集成測試也能夠連接到這個代理,那就更好了。
如果按照這些步驟進行,確實存在不少工作,但是它的確有效,而且這個過程的任何優化都讓你進一步徹底了解整個系統。有時候如果公司系統已經出現問題,而且可能會影響客戶時,如果按照這個流程進行可能使事情好轉,我寧愿完全控制和使用這個過程,而不是為了表面的節省幾天或幾周時間的方式。如果你更多地是激進的做事方式,你的老板也同意,也許那是可以接受的高風險方式,但是大多數公司寧愿采取稍慢一點,更穩健的重構之路。
上一篇: 手動測試無法被取代的12個重要理由
下一篇: 騰科快訊-API說明文檔相關最佳實踐綜述