重構項目,你真的准備好了嗎


我相信每個接受過老項目的程序員可能都吐槽過“前人的代碼都是屎”。一個已經有些年頭的項目,幾乎肯定可以看到——到處拷貝來拷貝去的代碼,隨處可見的拼寫錯誤,頭重腳輕的函數……再看一看當年的提交者,可能是公司里的元老,甚至是大boss,不禁心里暗暗的鄙視,懷疑是否自己進錯了公司。

而你被分配到接管這坨“屎”一般的代碼,並且要在上面添加更多的功能,每次的增刪代碼都讓你如履薄冰,每次遇到原來代碼里的bug都讓你的發際線再次上揚。

終於有一天,你忍不住了,腦子里面滿滿都是一個念頭——我要重寫這個代碼。然后你真的這么做了,花了整整一個晚上/天/星期的時間,把代碼改成了你心中滿意的模樣。然后代碼上線了:

Happy Ending:重構的代碼獲得了同事的交口稱贊,大家紛紛誇你代碼比以前好寫多了。

Normal Ending:過了幾個月,你發現重構的代碼又不行了,加一個新功能費死勁了,於是你又在籌划下一次重構。

Bad Ending:重構的代碼上線后,bug不斷,老板奪命連環call讓你連夜修補,你發現老代碼這么寫不是沒有道理的。

這樣的故事每個經手過老項目的程序員可能都多少有類似的體會,在我的職業生涯中也經歷了屈指可數的幾次重構,然而每一次的重構經歷幾乎都踩到了各種各樣的坑。

你真的需要重構嗎

在重構項目之前,一定要再三的問自己(和自己的組員)這個問題:我們真的需要重構嗎?

重構項目,在只是重構的前提下,對於公司的收益來說是——0,因為你的產品的用戶,他們並不會為你的重構行為來買賬,對於他們來說,你的源代碼寫的好看與否根本無所謂,對他們重要的是產品本身有沒有改進。對於公司來說,重構行為不但沒有帶來任何利益,反而消耗了程序員資源,對於公司來說是損失。

一個互聯網產品的生命周期可能就只有短短的幾年,放長一點看,現在寫的代碼可能過幾年就會毫無用處,在這樣的前提下,現有項目的重構,一定是建立在項目本身還十分有前景的基礎上,這個項目將來還有多少潛力,值不值得去重構?如果這個產品本身並沒有什么可做的了,那么是否還值得花時間去重構它?

為什么需要進行項目重構

每個項目重構的理由各不相同,但個人總結來主要是以下兩點

  1. 原來的項目漏洞太多,或者穩定性太差,當前的框架很難徹底根治。
  2. 新的項目需求,原有的程序框架已經無法滿足。

假設你的項目沒有很多bug,穩定性也很好,或者暫時沒有在現有框架下很難實現的新的需求,那么不建議進行項目重構。

我在上一家公司的SEM組工作時,經歷的第一次重構,是將后台的競價計算出的競價的結果,由數據庫的表(Table)存儲改成了推送到隊列系統(RabbitMQ)。后台競價程序算出的競價結果需要由另一個上傳程序上傳到Adwords等競價平台,我們在過去的做法是在數據庫建立了一張表,競價程序將算出的新競價存儲在其中,上傳程序則定期的去查詢表中的新加入的記錄,將其成批上傳,並在上傳后刪除。那么為何要進行這次重構?

  1. 隨着公司的投放的廣告詞增加,單一上傳程序實例很難在短時間內上傳所有的競價,但是如果運行多個上傳程序的實例,則會出現多個示例同時查詢新加入競價並上傳,刪除同一記錄造成數據庫死鎖。(1. 原來的項目漏洞太多,或者穩定性太差,當前的框架很難徹底根治。
  2. 新業務需求需要計算另一種格式的競價,如果繼續使用數據庫表來存儲,則要么需要對已有的表進行字段擴容/修改,要么建立新的表單。但是當時已經預見將來可能會支持更多格式的競價,於是數據庫表的存儲方式將不再靈活。(2. 新的項目需求,原有的程序框架已經無法滿足。

重構項目

經過再三衡量,我們終於還是決定重構項目,恭喜你,你將有一段踩坑之旅。

重構項目之前

重構項目的第一步是要了解項目。

重構時最容易發生的一類錯誤是沒有能夠完全的將原來的功能忠實的重現出來。很多開發者並不是手頭的項目的原作者,並且項目也經過了很長時間的迭代,當代碼越滾越大的時候,幾乎沒有人(包括原作者和產品經理)能夠完全了解項目到底包含了哪些內容。當你看到重構后的功能和原來一模一樣,並且測試人員也沒有測出問題的時候,說不定哪個猴年馬月添加進來的特殊功能,悄悄的被你干掉了。等到上線后,這個特殊功能的用戶突然發現功能沒了,於是過來投訴。

重構ING —— 測試

如果說什么是重構中最重要的第一步, 我認為是測試。

如果原來的代碼沒有單元測試、集成測試,有條件的話一定要補充上。為什么測試如此重要?打一個比好,重構就好像對着一把老鑰匙來配新鑰匙,而測試代碼則是老鑰匙的模子,我們做出來的新鑰匙要能夠和這個模子全對上。這個模子越詳細,則新鑰匙可以正常開鎖的概率越大。

回想我在過去的重構中出現的一次重大失誤,便是在重構過程中,有一個原來的單元測試出現了錯誤,原本的斷言是結果為NULL,但是我的結果是0,當時覺得可能兩種結果都可以,於是錯誤的選擇了將單元測試的結果“改正”,結果在代碼上線后,0的結果造成了程序輸出和之前相比大不相同。總結一下:1. 如果有集成測試,則這樣的錯誤可以在上線之前發現。2. 應該相信原來的單元測試集,而不應該“想當然”的去認為自己重構的邏輯正確。

重構ING —— 分支

代碼重構的過程中,一定不建議先刪除代碼全部重寫。比較推薦的是先拷貝出一個新的函數/文件/文件夾,然后寫全新的代碼。為什么要這么做?

  1. 在寫新代碼的時候可以一邊寫一邊參照原來的代碼。
  2. 新代碼的代碼審查(Code Review)會比較干凈。
  3. 項目管理工具(Git,SVN)的歷史比較干凈。

回到我上面說的由數據庫的表(Table)存儲改成了推送到隊列系統(RabbitMQ)的重構,當時我的做法是,在競價程序端,重新實現了輸出的函數,使得競價結果可以改為推送到隊列系統。而在上傳程序端,則重新實現了一個新的程序,只從消息隊列中消費推送的消息,然后上傳到Adwords等廣告平台。原有的舊上傳程序則沒有改動絲毫。

重構項目的上線 —— 開關

稍微大一些的重構,我會比較推薦使用程序開關,使用一些控制參數來控制邏輯入口是用老代碼還是新代碼,這樣在線上出現了問題,可以及時的調整控制參數,迅速的回滾到老的邏輯。

如果程序運行的結果本身就是不確定的,不容易看出重構的錯誤,甚至推薦在重構的入口處設置A/B測試,這樣在線上讓一部分流量先走重構后的邏輯,同時將新/老邏輯的流量標記成不同的測試bucket,可以在數據測量平台上看到新老代碼的表現如何。如果新代碼的表現合理,則可以不斷加大新代碼的流量覆蓋,直到100%。

在我上面提到的重構中,我選擇在競價程序計算段創建了一個新的A/B測試,對照組采用將競價結果寫到數據庫的方法,實驗組則將競價結果發送到消息隊列。同時在生產環境中,舊的和新的上傳程序都在同時運行。在剛上線的時候,我選擇將1%的競價結果推送到消息隊列中,然后觀察新的上傳程序能否將消息隊列中的消息消耗掉。同時,在產品的監視頁面,對對照組和實驗組的競價結果進行分析,確認兩個組的競價結果並沒有明顯的差別。

總結

總結一下個人的重構心得,重構前是否必要,重構中做好測試、分支、開關。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM