經典著做《重構》這本書中有這麼一段話:程序員
一開始,我所作的重構都停留在細枝末節上。隨着代碼趨向簡潔,我發現本身能夠看到一些設計層面的東西了,這些是我之前理解不到的,若是沒有重構,我達不到這種高度。數據庫
重構,着實是一件讓程序員興奮的事情。緩存
今年年初,咱們團隊完成了一個複雜項目的重構工做,它屬於廣告系統最核心的引擎部分,大概有 300 多個文件,3 萬多行代碼。架構
從技術方案設計到最終全量上線僅僅花了 1 個月左右的時間,並且沒有產生事故。框架
這應該是我 8 年程序生涯中,經歷過的最大型的同時最成功的一次重構項目:速度足夠快、計劃比較周全、質量過關。模塊化
咱們的廣告引擎在此次重構前大概經歷了1年半時間的迭代,初期針對的是搜索場景,業務單一,流程清晰。性能
2019年開始,公司的廣告業務開始快速擴張,收入幾乎是指數級的增加。在這個過程當中,咱們的廣告引擎面臨了兩個挑戰:測試
一、業務場景開始變得複雜,除了搜索廣告,還須要支持信息流推薦以及類似推薦場景。設計
二、廣告流量開始快速增長,除了知足功能性需求,還須要兼顧好性能。日誌
通過梳理,整個引擎有大部分邏輯是能夠公用的,所以咱們定義了一個主體框架,同時將可擴展部分進行了抽象。這樣,各個場景可以根據自身業務的特殊性實現某些公共接口便可。另外,從性能角度考慮,咱們犧牲了一些代碼可讀性,把某些邏輯並行化了。
隨着業務的發展,搜索場景開始進入快速迭代期,新增策略愈來愈多,咱們的主體框架也是在這個時候逐漸變得不靈活。
若是動主體框架,搜索之外的場景都須要跟着重構。 在業務的快速發展期,工期根本不容許,所以咱們只能在現有框架上進行補丁式的開發。 這樣,帶來了兩個很明顯的問題:
一、爲了兼容搜索的特殊邏輯,咱們須要在其餘場景中增長各類 if 判斷來繞過這些邏輯。
二、廣告策略愈來愈多,累計了幾十個,當框架失去清晰的結構後,有些策略的實現開始變得定製化,缺乏層次化的劃分和可插拔式的抽象設計。
在這樣的背景下,隨着改動的積累,代碼開始偏離了設計的初衷,技術債務愈來愈重。可是,咱們又始終找不到合適的時機進行重構。
起色出如今 2019 年年末,因爲廣告業務的特殊性,流量開始天然走低,另外產品運營團隊將重心放在了第 2 年的工做規劃上,所以給了咱們很是好的窗口期開始這次重構。
咱們將工期定成了 1 個月,最終僅比預期晚上線了一天,雖然出現了兩個線上問題,可是在灰度期都及時發現和修復了,並無形成線上事故。
整體來講,這是一次難度頗大而且比較成功的重構項目,下面詳細說一下我從這個項目中吸收到的寶貴經驗。
此次重構的代碼量很大,3 萬多行,並且是廣告系統最核心的引擎部分。啓動前,咱們能預期到下面這些困難:
一、業務側的阻力:廣告是極其以業務爲導向的,本次重構雖然能帶來長期研發效率的提高,可是無法直接提高業務收益,並且開發週期不會過短,如何才能獲得業務同窗的支持?
二、技術側的顧慮 :重構一旦引發線上事故,公司是有處罰制度的,如何讓你們輕裝上陣?同時,重構過程當中若是還有很是重的業務迭代穿插,交付時間沒人敢保證,質量也很可貴到控制。
針對這兩方的顧慮,我認爲下面這幾項工做起到了很關鍵的做用。
一、讓全部人看到痛點
前面提到:隨着業務迭代,咱們廣告引擎的主體框架已經變得模糊不清,另外幾十個廣告策略散落在不一樣的業務場景中,配置凌亂。
針對這兩個痛點,咱們提早1個月啓動了現有業務的梳理,走讀舊代碼、同時翻閱之前的需求文檔,最終咱們將不一樣場景的核心流程以及廣告策略歸類成了一張清晰的表格。
正是這一張表格,讓技術和產品第一次很清晰地看到了咱們引擎部分的全貌,體會到了業務的複雜度以及當前技術上的瓶頸。
二、明確重構的目標和價值
讓全部人感覺到痛點後,咱們規劃了本次重構的兩個核心目標:
一、主體框架的重構:將主流程模塊化,從新定義上下層協議,確保接口清晰;各層級內部也須要作好抽象,具有良好的擴展性。
二、策略靈活可配置:將廣告策略按照業務意圖進行歸類抽象,策略的執行條件動態可配置,同時策略可任意插拔。
此外,咱們將這兩個核心目標完成後可帶來的預期收益進行了細化:
一、技術收益:代碼結構更清晰,更容易理解和維護;可擴展性加強,引擎的開發效率將進一步提高。
二、業務收益:策略能作到更細粒度的配置和擴展,對業務支持更友好;研發提效後能進一步加快業務的迭代速度。
將重構的價值同步給你們後,進一步提高了全部人的興奮度,讓你們有了更強的動力參與進來。
三、總體節奏的把控
總體節奏的把控也是很是重要的一環,能讓全部人對這件事情有一個時間上的預期。
首先,咱們將工期定成了 1 個月,一方面考慮了業務側能夠接受的最大週期,技術上也但願速戰速決;另外一方面,春節即未來臨,咱們必須趕在公司封網前上線,同時預留出1-2周的 buffer 以防意外狀況發生。
此外,咱們和業務側達成了一致:重構期間,引擎部分的非緊急需求一概不接,這樣可最大限度地減小並行開發和代碼衝突,讓團隊精力更集中。
此次重構可以實施得如此順利,有 4 點我認爲頗有價值的經驗跟你們分享下。
一、高質量的技術設計方案
這一點得益於平常的要求,針對開發週期超過3天的項目咱們都會進行技術方案設計,本次重構固然也不例外。
框架部分的總體架構、模塊之間的協議設計、以及策略的可擴展性設計是本次技術方案的重點,團隊先後討論了不下3次。
在大方案定稿後,團隊進一步對數據庫、接口字段、緩存結構、日誌埋點等公共部分進行了細化,由於涉及到多人協做開發,團隊約定以文檔做爲溝通界面,文檔始終保持和代碼同步。
在這樣的高要求下,團隊產出了 5000 多字的技術方案文檔,合計 36 頁,這些爲總體質量的保障打下了很好的基礎。
二、 預重構出框架性代碼
這一個 PR 很是關鍵,是咱們從技術方案落地到代碼最重要的一步。咱們把重構後的包結構、模塊劃分、各層之間的API定義、不一樣廣告策略的抽象進行了梳理,先忽略實現的細節。
這樣主體代碼基本成型,能很清楚地描繪出咱們理想中的框架。而後,咱們組織了屢次集中代碼審查,最終造成了統一意見。
這一步能很好地避免過早陷入實現細節,致使主體框架關注不夠、代碼不穩固,後期再返工反而會拖累效率。
三、 頻繁溝通和成對代碼 Review 機制
進入到細節實現階段後,很重要的一點是:對現有邏輯的理解。引擎代碼通過一年半的迭代,歷史上被不少人開發過,可是本次只有 3 個同窗參與重構。
整個過程當中,咱們遇到任何代碼邏輯不明確的地方,都是反覆溝通和求證,不主觀猜測,這一份謹慎其實很關鍵。
另外在代碼審查上,咱們按模塊分配了對這塊業務比較熟悉的同窗來負責,成對搭配,機制靈活。
四、 有效的測試方案
重構未動,測試先行。這個原則是《重構》一書中重點強調的,也是咱們本次技術方案討論的重點,我這裏單獨拎出來詳細展開下。
首先,咱們前期便約定好:不動任何老代碼,徹底建新的 package 進行重構。這樣方便比對重構先後的結果,同時進行線上灰度實驗。
測試方案上,如下 4 點值得借鑑:
一、端到端測試:本次重構不涉及功能性的調整,所以外層API的行爲是不會有任何變化的,這樣端到端的測試方法最爲有效,這個是研發和QA測試最主要的手段。
二、冒煙測試:QA同窗提供冒煙 Case,由研發同窗進行冒煙,研發提測前必須保證全部冒煙 Case 執行經過。這一點在大部分互聯網公司都不常見,可是對於大型項目絕對有效。
三、沙箱環境雙流程驗證:前面提到咱們重構先後的代碼都保留了,所以能夠經過腳本抓取線上環境的入參做爲case,而後用自動化的方式對 API 的返回字段進行逐一比對。
四、線上環境灰度實驗:灰度對於重構很是重要,咱們利用已有的ABTest平臺,逐步放開灰度流量,從5%、到10%、到30%、最後到100%,制定了很謹慎的放量節奏,而後經過日誌以及業務指標監控進行驗證。
回顧整個重構的過程,總結成下面 7 個關鍵點:
一、把握好重構時機
二、前期梳理很重要,先找到痛點
三、明確出目標和價值,讓你們興奮起來
四、不宜長線做戰,不宜和業務並行
五、須要高質量的技術方案
六、重構未動,測試先行
七、當心求證,爲每行代碼負責
固然,最關鍵的因素仍是人,大型項目重構極其考驗團隊的協做能力,若是每一個人都很靠譜,重構就已經成功了一半。
做者簡介:985碩士,前亞馬遜工程師,現58轉轉技術總監
歡迎掃描下方的二維碼,關注個人我的公衆號:IT人的職場進階