每一個程序員,項目經理或團隊負責人的生命週期中至少發生一次,你接手一坨超過百萬行代碼的系統,原來的程序員好久之前就離職,如今也許正在某個陽光明媚的地方度假,文檔(若是有的話)最有可能的狀況就是與現有的系統不一樣步。
html
而你的工做則是帶領團隊脫離這個混亂。linux
在經歷逃離的本能迴應以後,您開始對項目進行了瞭解,公司高層領導是不能容忍項目失敗的狀況。然而,經過你手頭現有的東西,失敗是大機率發生的事件。那麼該如何應對?程序員
我有幸(或不幸)已經經歷過若干次相似的經歷,我和一小部分朋友發現,若是可以把這些垃圾代碼變成健康的可維護的項目,其實是很是值得一試的事情。如下是咱們總結改進舊版代碼庫的一些經驗(或者叫軍規)。數據庫
一、數據備份後端
在開始作任何事情以前,您須要備份全部可能相關的內容,這樣能夠確保無論發生什麼狀況不會丟失數據。咱們很難記得天天修改了哪些東西,特別是配置數據容易受到這種問題的影響,配置一般不會進行版本控制,若是可以進行按期備份,那則能夠規避不少麻煩。把全部東西複製到一個很是安全的地方,除非它是隻讀模式,不然永遠不會觸摸到。安全
二、重要的先決條件,構建一個真實的仿真環境服務器
我以前的文章中錯過了這一步,假設這個環境已經存在了,但許多 HN 網友指出了這一點,他們是絕對正確的。數據結構
第一步是確保你知道如今正在生產環境運行的是什麼,這意味着您須要可以構建一個軟件版本 —— 和您的真實環境保持一致 —— 相同的軟件環境與二進制版本。架構
若是你找不到一個方法來實現這一點,那麼若是你提交代碼到生產環境,就可能會遇到一些使人不快的意外。確保新的代碼在合適環境儘量地被測試,而後你纔會有足夠信心將其運行到生產環境。上線時作好準備能夠隨時切換回老的代碼,並確保經過日誌記錄了相關重要內容,以便在後續排查問題能派上用場。模塊化
三、凍結 DB 修改
儘量凍結數據庫修改,直到完成第一階段的改進,直到團隊對代碼庫已經有了完全的瞭解,遺留代碼已經棄之身後時,才考慮修改數據庫結構。在此以前任何的數據庫修改可能會致使一些棘手的問題,你失去了並行運行舊系統和新的代碼庫的能力。保持 DB 徹底不變,您能夠比較新的業務邏輯代碼與舊的業務邏輯代碼,若是全部這些效果都與預期同樣,則應該徹底沒有區別。
四、編寫測試
在進行任何修改以前,編寫儘量多的端到端以及集成測試,確保這些測試可以產生正確的輸出,並覆蓋全部潛在的狀況。
這些測試將具備兩個重要功能:幫助在早期階段清除任何誤解,另一方面,一旦您開始編寫新代碼來替換舊代碼,這些測試將能夠更好保護您的系統。
自動化您的全部測試,若是您已經有 CI 的經驗則儘快使用它,並確保您的測試運行足夠快,以便在每次提交後運行全套測試。
五、Instrumentation 和日誌
若是舊平臺仍然能夠增長 Instrumentation,在一個全新的數據庫表中執行此操做,爲您能夠考慮的每一個事件添加一個簡單的計數器,並添加一個單個函數來實現此功能,以根據事件的名稱來增長這些計數器。
這樣,您可使用一些額外的代碼行實現帶有時間戳的事件日誌,您將瞭解到有多少事件致使另外一種事件。一個例子:用戶打開應用程序,用戶關閉應用程序。若是兩個事件應該致使一些後端請求,那麼這兩個計數器應該在長期上保持不變,差別是當前打開的應用程序的數量。若是您看到更多的應用程序打開,而不是應用程序關閉,你知道必須有另外一種應用程序結束的方式(例如崩潰)。
這個簡單的技巧能夠將每一個後端應用程序變成一個相似的簿記(bookkeeping)系統,就像一個真正的簿記系統那樣,全部的數字必須匹配,確保它們在全部用到的地方沒有問題。
隨着時間的推移,這個系統將監控健康方面變得很是寶貴,而且將成爲源代碼控制系統變動日誌的一個很好的伴侶,您能夠在其中肯定每一個錯誤引入的時間點,以及對各類狀況產生影響的計數。
我一般保留這些計數器的分辨率爲 5 分鐘(所以每小時記錄 12 次),但若是你的系統有更少或更多事件,則可能須要修改這個時間間隔。全部計數器使用同一個數據庫表,所以每一個計數器只是該表中的一列。
六、每次只修改一個點
在添加新功能或修復錯誤的同時,不要陷入同時改進代碼以及修改其運行的平臺的陷阱。這會致使不少頭大的問題。
七、平臺更改
若是您決定將應用程序遷移到另外一個平臺,那麼請先執行此操做,但要保持一切功能徹底同樣。你能夠添加更多的文檔或測試,但不能超過這一點,全部業務邏輯和相互依賴關係應該保持原樣。
八、架構變化
接下來要解決的是改變應用程序的架構(若是須要)。在這個時候,您能夠隨意更改代碼的較高級別結構,一般經過減小模塊之間的水平連接數量,從而減小與最終用戶進行任何交互時代碼活動的範圍。若是舊代碼本質上是一體的,如今將是一個很好的時機使其更加模塊化,將大型功能分解成較小的功能,可是保留變量和數據結構的名稱。
HN 網友 mannykannot 指出,架構修改並不老是可行,若是特別不幸運,那麼可能須要很是深刻理解代碼才能進行任何架構更改。我贊成這一點,所以我作個小的補充,若是您同時進行高級別更改和低級別更改,至少須要將其限制在一個文件,或最壞狀況下限制在一個子系統,以便儘量限制更改的範圍。不然你可能很難調試剛纔所作的更改。
九、低級重構
到目前爲止,您應該對每一個模塊的功能有很好的瞭解,併爲實際工做作好準備:重構代碼以提升可維護性,並使代碼具有擴展新的功能的能力。這極可能是項目中耗時最多的一部分,文檔須要隨之進行,在完整編寫文檔介紹並完全瞭解一個模塊以前,不要隨意更改模塊。
這個階段也能夠修改變量和函數命名、修改數據結構,以提升代碼清晰度和一致性。記得添加相關測試代碼(根據須要,可進行單元測試)。
十、修復 bug
如今你準備好進行一些最終用戶可見的變化,第一件事情將是修復多年來積累在隊列中的 bug。像往常同樣,首先確認 bug 仍然存在,而後編寫一個測試並修復 bug,您的持續集成和端到端的測試應幫您避免因爲缺少理解或某些錯誤而致使的任何錯誤及外圍問題。
十一、數據庫升級
若是上述工做都已經完成,你能夠再次擁有可靠且可維護的代碼庫,您能夠選擇更改數據庫 schema 甚至替換數據庫。已經完成的上述工做都將有助於您以無負擔的方式進行變革,而無需擔憂任何意外,您可使用新的代碼和全部的測試來測試新的數據庫,以確保您的遷移沒有任何問題。
在路線圖上前行
恭喜,到這裏您已經走出了叢林,如今已經準備好能夠實施任何新功能了。
不要嘗試完全重寫
完全重寫是一種幾乎保證會失敗的項目。一方面,你是在未知的領域開始,你甚至會不知道要重構什麼,另外一方面,你也將全部的問題推到最後一天,就在你用新系統啓用以前的那一天。很悲劇的是,那也是你失敗的時刻。業務邏輯的假設最終會證明存在問題,那時您將忽然瞭解到爲何舊系統會用某種奇怪的方式來工做,最終也會意識到能將舊系統放在一塊兒工做的人也不都是白癡。 若是你真的想要將公司(以及你本身的信譽)帶向一個泥潭,就來一個完全大重寫吧,但若是你足夠聰明,完全重寫系統一般不會成爲桌上的一個討論選項。
替代方案:迭代式改進
要解開這些線團最快方法就是從你已經理解的代碼入手(它多是一個外圍設備,但也多是一些核心模塊),並在它的舊的上下文的範圍內嘗試逐步改進。
若是舊的構建工具再也不可用,您將不得不使用一些技巧(見下文),但至少在您開始更改時,儘量多地保持舊的系統工做。一個典型的提交一般只包含數行代碼。
發佈!
全部修改儘量發佈到生產環境,即便修改的代碼是最終用戶不可見的,由於當你對系統瞭解不足時,只有生產環境纔會告訴你新的修改哪裏會有問題。若是這個問題只是在小的改變以後出現,你將得到幾個優點:
很容易弄清楚出了什麼問題
您將處於改善流程的良好狀態
您應該當即更新文檔,以記錄得到的新看法
合理使用代理服務器
若是您正在重構一個 Web 系統,感謝上帝,你能夠在最終用戶和舊系統之間部署一個代理服務器。您能夠精確控制每一個 URL 哪些請求進入舊系統,哪些請求路由到新系統,從而能夠更輕鬆,更精細地控制運行的內容。
若是您的代理足夠強大,您甚至能夠控制將某個 URL 必定百分比的流量發送到新系統,以便觀察新系統的運行狀況。若是您的集成測試也可以鏈接到這個代理那就更好了。
頗有道理,但這一切須要太多的時間!
那麼這取決於你如何看待它。若是按照這些步驟確實存在很多工做,可是它的確有效,並且這個過程的任何優化都讓你進一步完全瞭解整個系統。我我的在這方面也有一個很好的聲譽,我真的不但願這樣的工做中出現任何負面的問題。
有時候若是公司系統已經出現問題,並且可能會影響客戶時,若是按照這個流程可能使事情好轉,我寧願徹底控制和和使用這個過程,而不是爲了表面的節省幾天或幾周的時間的方式。若是你更多地是牛仔的作事方式,你的老闆也贊成 —— 那麼也許那是能夠接受的高風險方式,可是大多數公司寧願採起稍慢一點,更穩健的重構之路。
本文地址:http://www.linuxprobe.com/change-code-experience.html