淺談遺留代碼的重構(gòu)

refactor-decision-list

背景

《重構(gòu)》誕生至今有近17個(gè)年頭了,日常開發(fā)中大家談到重構(gòu),要么非常隨意,認(rèn)為重構(gòu)就是改代碼;要么非常謹(jǐn)慎,把重構(gòu)描述成焦油坑,像瘟神一樣敬而遠(yuǎn)之。我們應(yīng)該怎么看重構(gòu)呢?針對(duì)最具挑戰(zhàn)的遺留代碼的重構(gòu),有哪些需要注意的呢?

談?wù)撊魏问虑?,都該有它的上下文。本文談?wù)摰募夹g(shù)背景是大型通信類產(chǎn)品,對(duì)于互聯(lián)網(wǎng)產(chǎn)品不一定適用。另外,本文也不會(huì)涉及重構(gòu)技術(shù),有興趣讀者可以讀《重構(gòu)》或者《Effective Refactoring in C++》。

重構(gòu)與收拾屋子

為什么收拾屋子?

住酒店,有服務(wù)生幫我們收拾房間,在家需要自己收拾,因?yàn)樽约哼€要住很長時(shí)間。屋子干凈了,還是有好處的,東西就好找了,哪些東西放的位置不對(duì),也更容易識(shí)別出來。

重構(gòu)亦是。我們看看《重構(gòu)》中Martin Fowler對(duì)“重構(gòu)”的定義:

名詞定義:對(duì)軟件內(nèi)部結(jié)構(gòu)的一種調(diào)整,目的是在不改變軟件可觀察行為的前提下,提高其可理解性,降低其修改成本。

定義中給出了重構(gòu)的兩個(gè)目的(收益):

  1. 提高可理解性
  2. 降低修改成本

代碼編寫完成后,自己或者他人還會(huì)維護(hù)較長一段時(shí)間。重構(gòu),是期望代碼可以更多的被復(fù)用,有更長的服役時(shí)間。

如何收拾屋子?

根據(jù)屋子的布置(結(jié)構(gòu)),每件物品都有它應(yīng)該放置的位置,發(fā)現(xiàn)不在位置上,就調(diào)整調(diào)整。

重構(gòu)亦是。重構(gòu)是對(duì)軟件內(nèi)部結(jié)構(gòu)的一種調(diào)整。好的軟件在滿足系統(tǒng)功能正確性、性能等要素同時(shí),還需考慮軟件的擴(kuò)展性、伸縮性和可讀性。所以它應(yīng)該是有架構(gòu)的,體現(xiàn)為橫向切分的不同層次和縱向切分的不同模塊,及更細(xì)粒度的類、方法、函數(shù)。重構(gòu)就是根據(jù)軟件本身應(yīng)該的結(jié)構(gòu),對(duì)代碼元素進(jìn)行調(diào)整,放到合適的位置。

什么時(shí)候收拾?

發(fā)現(xiàn)屋子臟了、亂了就收拾收拾,工作忙,就周末收拾,工作輕松些就每天順手收拾。但是房子臟,那是欠的債,遲早需要還的。

重構(gòu)亦是。伴隨著開發(fā)的過程,重構(gòu)應(yīng)該是個(gè)習(xí)慣,發(fā)現(xiàn)代碼有壞味道了,就及時(shí)消除掉。交付壓力太大時(shí),就稍微緩緩,等稍能喘口氣,就趕緊把債還掉,不然積少成多,可能就很難收拾了。

Martin Flower 給出了四個(gè)重構(gòu)時(shí)機(jī):

  • 產(chǎn)生重復(fù)代碼時(shí),
  • 新增功能之前
  • 修改故障之前
  • 代碼走查之后

Kent Beck提出的XP(eXtreme Programming)中,TDD(Test Driven Development)實(shí)踐更是把重構(gòu)作為開發(fā)過程中的一部分:
refactor-decision-list

逢年過節(jié),你會(huì)來個(gè)大掃除,徹底把屋子調(diào)整調(diào)整。

重構(gòu)亦是。時(shí)間久了,新人多了,交付壓力大了,代碼難免會(huì)產(chǎn)生一些腐化,這些可以通過集中重構(gòu),徹底清洗清洗。

發(fā)現(xiàn)房間布置,格調(diào)有點(diǎn)跟不上時(shí)代,需要整的大些,就是裝修了。要看自己有沒有地兒住,有沒有時(shí)間折騰。

重構(gòu)亦是。當(dāng)系統(tǒng)新增功能改動(dòng)很大;當(dāng)系統(tǒng)性能成為瓶頸,無法忍受;當(dāng)代碼穿著一層層補(bǔ)丁貼成的外衣,仍到處漏風(fēng)時(shí),此時(shí)縫縫補(bǔ)補(bǔ)此時(shí)已無濟(jì)于事,需要更大的調(diào)整,才能根除這些問題。當(dāng)然此時(shí)系統(tǒng)已經(jīng)上線,用戶嗷嗷待哺,你需要有替代方案過度,需要爭取時(shí)間調(diào)整。

再看一則小故事:

一個(gè)建筑隊(duì),全國各地到處跑,住的是一個(gè)茅草屋。遮風(fēng)避雨是沒有問題的,只是每到一地都要重蓋,一到下大雨還到處漏水。修修補(bǔ)補(bǔ)的辦法總有的,但總是很狼狽。有一天,有人告訴工程隊(duì),可以用可組合的鐵框?yàn)楣羌?,彩鋼為夾層,這樣既可以連續(xù)拆裝,節(jié)省成本,房子也結(jié)實(shí)牢固。 工人們開始擔(dān)心了:“怎么組裝啊,看上去好麻煩!”。工程隊(duì)的老人也替工頭擔(dān)心,“那得花多少錢啊”,“會(huì)不會(huì)住不習(xí)慣”,工頭出去考察了一番,回來一狠心,整了一套,雖然過程有些費(fèi)勁,結(jié)果收益還是杠杠的。后來大家?guī)缀醵纪嗽?jīng)住過茅草屋。

當(dāng)然,這里講的不再是重構(gòu)了,這種推倒重來的做法,我們叫它再工程(re-engineering)。與重構(gòu)相比,他的風(fēng)險(xiǎn)更大,成本更高。但所謂高風(fēng)險(xiǎn),高回報(bào),如果它能帶來更大的收益,甚至顛覆性創(chuàng)新,我們也值得去做。

再工程不是本文討論的重點(diǎn),打??!

遺留代碼重構(gòu)

遺留代碼的重構(gòu)屬于上文中提到的“大掃除”或“裝修”場景。對(duì)遺留代碼進(jìn)行重構(gòu),很容易形成“吃力不討好”的局面,究其原因,我們先回顧下重構(gòu)的目的:

  • 提高可理解性
  • 降低修改成本

這兩點(diǎn),無論從可驗(yàn)證性,還是可被度量角度都比較困難。如果項(xiàng)目僅以短期結(jié)果度量,重構(gòu)成果很難自證明。再加之改動(dòng)較大,可能引入一系列不確定因素,無功還有過,自然吃力不討好,所以我們?cè)谶M(jìn)行遺留代碼重構(gòu)時(shí)要充分考慮收益和風(fēng)險(xiǎn),收益盡量考慮可被驗(yàn)證、被度量要素,風(fēng)險(xiǎn)充分考慮成本、時(shí)間、范圍等項(xiàng)目關(guān)注要素。在一個(gè)工程師話語權(quán)不是那么大的公司,這一點(diǎn)尤為重要。

基于上述場景分析,定義了遺留代碼重構(gòu)決策表:
refactor-decision-list

從重構(gòu)帶來的收益和風(fēng)險(xiǎn)兩個(gè)維度,綜合考量、打分,給出一個(gè)簡單、可度量、易被執(zhí)行的決策表。下面我們逐一分析下每條決策項(xiàng):

收益

  1. 性能瓶頸

看到這條,你一定很不解:一些經(jīng)驗(yàn)也告訴我們,軟件的擴(kuò)展性,常會(huì)犧牲一些性能;再看看《重構(gòu)》書中一段描述:

為了讓軟件易于理解,你常會(huì)做出一些使程序運(yùn)行變慢的修改

而更好的可讀性及好的擴(kuò)展性,恰是重構(gòu)追求的,豈不是自相矛盾?

關(guān)于性能優(yōu)化,我會(huì)在另一篇文章中詳細(xì)闡述,我們先看結(jié)果,重構(gòu)會(huì)給我們帶來如下在性能方面的改善:

  • 結(jié)構(gòu)良好的代碼,在性能分析時(shí)有更細(xì)的粒度,更容易發(fā)現(xiàn)性能瓶頸
  • 邏輯清晰的軟件,更容易反映軟件業(yè)務(wù)本質(zhì),而清楚我們真正要解決的問題,對(duì)性能往往有意想不到的提升
  • 對(duì)軟件結(jié)構(gòu)的調(diào)整,使得對(duì)象及對(duì)象之間的關(guān)系更合理,可以大量減少內(nèi)存浪費(fèi)
  • 多核、分布式場景下,性能的瓶頸往往不是計(jì)算本身,而是不合理的調(diào)度,對(duì)軟件結(jié)構(gòu)的調(diào)整,可以從根本上解除該部分約束

另外,性能優(yōu)化很容易被度量,該部分產(chǎn)生的成果很容易被項(xiàng)目接受。

  1. 高危、高頻故障

看到這條,你又開始不解了,重構(gòu)是“在不改變軟件可觀察行為的前提下”進(jìn)行的,而故障本身就是軟件在特定場景下的錯(cuò)誤行為,所以重構(gòu)是改變不了故障本身的。那對(duì)高危、高頻故障模塊,重構(gòu)的價(jià)值在哪里呢?

  • 某模塊故障總是消滅一波,又來一波,攻不死,殺不完,一方面,說明該模塊需求變化還是很頻繁的,另一方面,說明模塊設(shè)計(jì)出了問題,要么是邏輯混亂,要么是內(nèi)部耦合太大,這些都可以通過重構(gòu)來消除。
  • 重構(gòu)的一個(gè)目的是“提高可理解性”,邏輯清晰、整潔的代碼,使故障就像白墻上的蒼蠅,很容易發(fā)現(xiàn),解決。
  • 重構(gòu)的另一個(gè)目的是“降低修改成本”,軟件容易修改,需要軟件遵循開放封閉原則,修改代碼不影響原有功能,也就避免了增加功能、修改故障引入的新問題。
  • 故障數(shù)是一個(gè)容易度量的指標(biāo),效果很容易可視化。
  1. 新功能擴(kuò)展困難

軟件之所以需要設(shè)計(jì),而不僅僅實(shí)現(xiàn)功能,一方面可以被復(fù)用;另一方面容易增加功能。新增功能困難,并非是無法增加功能,而是,增加功能需要改動(dòng)很多代碼,從而帶來更多風(fēng)險(xiǎn),更大維護(hù)成本。 重構(gòu)通過對(duì)軟件內(nèi)部結(jié)構(gòu)的調(diào)整,不斷消除重復(fù),劃分不同層次,使得新增功能對(duì)原有功能影響盡量小。

  1. 代碼邏輯混亂,可讀性差

編寫易讀、易理解的代碼,并不像說的那么容易,因?yàn)樗欠粗庇X的,它產(chǎn)生的價(jià)值不是對(duì)當(dāng)下的自己,而是以后的自己或者其他人,需要換位思考。

簡單分享下自己對(duì)編碼認(rèn)識(shí)的幾個(gè)階段:

  1. 實(shí)現(xiàn)功能,追求性能
  2. 考慮擴(kuò)展性,增加功能比較容易
  3. 考慮易理解,維護(hù)代碼比較容易
  4. 考慮易復(fù)用,除了自己,期望他人也可以用

重構(gòu)對(duì)易理解性帶來的收益:

  • 對(duì)代碼重構(gòu)的過程,是對(duì)代碼所表述業(yè)務(wù)邏輯再理解的過程。
  • 易理解的代碼,更容易發(fā)現(xiàn)業(yè)務(wù)本質(zhì)
  1. 人員能力提升

這里的人員能力提升包括兩個(gè)方面:

  1. 業(yè)務(wù)能力提升。重構(gòu)過程中是對(duì)業(yè)務(wù)邏輯再理解的過程,通過一層層抽絲剝繭,我們也更了解業(yè)務(wù)本身。
  2. 技術(shù)能力提升。無論是重構(gòu)到Clean Code,還是重構(gòu)到模式,我們的抽象能力、設(shè)計(jì)能力會(huì)伴隨著這個(gè)過程逐漸提升。

風(fēng)險(xiǎn)

任何一件事,當(dāng)我們看到收益的同時(shí),應(yīng)該評(píng)估它帶來的風(fēng)險(xiǎn)。對(duì)于遺留代碼的重構(gòu),在動(dòng)工之前,我們需要回答如下問題:

  • 重構(gòu)的主要目標(biāo)是什么?因?yàn)樵谥貥?gòu)過程中,難免會(huì)遇到抉擇和舍棄,如果沒想清楚我們的主要目標(biāo),容易搖擺不定或者迷失了方向。
  • 重構(gòu)的范圍是什么?重構(gòu)最容易掉入的一個(gè)陷阱就是,重構(gòu)范圍越來越大,大到無法收手。
  • 重構(gòu)的計(jì)劃是什么?雖然重構(gòu)過程中,有太多的不確定因素,極端場景下,重構(gòu)的結(jié)果給當(dāng)初認(rèn)為的完全不一樣,但我們確實(shí)需要一個(gè)時(shí)間盒,在它的約束下,我們更容易集中精力達(dá)成我們預(yù)期的目標(biāo)。
  • 重構(gòu)真的必要嗎?有沒有低成本的替代方案?雖然我們鼓勵(lì)用技術(shù)解決問題,但生活中的確存在很多在研發(fā)來看很重要,從商業(yè)角度“然并卵”的事。

想清楚上面的問題后,繼續(xù)考慮如下維度:

  1. 人員支撐情況

人是重構(gòu)的核心資源,靠譜的人才能做出靠譜的產(chǎn)品。一方面,重構(gòu)的質(zhì)量、完成的速度依賴人,另一方面,重構(gòu)過后代碼的維護(hù)及架構(gòu)的演進(jìn)也依賴人。需靠考慮如下幾個(gè)方面:

  • 重構(gòu)要求不能改變軟件的外部行為,我們還期望通過重構(gòu)可以簡化設(shè)計(jì),縮小業(yè)務(wù)與實(shí)現(xiàn)之間的Gap,這就需要有熟悉業(yè)務(wù)人員。你可能會(huì)說:“業(yè)務(wù)全在代碼里了,自己看不就行了”,說的沒錯(cuò),只是太累了
  • 嚴(yán)格按照重構(gòu)手法,基本可以做到重構(gòu)前后業(yè)務(wù)邏輯的一致,這就需要至少有人熟悉重構(gòu)技法。
  • 高效率來自專注,如果不能全身心投入,或者任務(wù)不斷切換,結(jié)果往往勞力又勞心。
  • 團(tuán)隊(duì)中有Tech Lead,不但可以幫助提升團(tuán)隊(duì)重構(gòu)技能,在團(tuán)隊(duì)產(chǎn)生技術(shù)爭執(zhí)時(shí),還可以進(jìn)行裁決。
  • QA是團(tuán)隊(duì)交付產(chǎn)品質(zhì)量的最后一道防線,如果重構(gòu)過程中,能不斷得到對(duì)重構(gòu)質(zhì)量的反饋,可以大大降低重構(gòu)帶來的風(fēng)險(xiǎn)。
  1. 重構(gòu)周期

每個(gè)產(chǎn)品都有版本計(jì)劃及市場使命。如果產(chǎn)品即將退市,對(duì)它進(jìn)行的重構(gòu),無疑是沒有任何意義的,因?yàn)橹貥?gòu)后的軟件已經(jīng)沒有上場表演的機(jī)會(huì)。重構(gòu)需要根據(jù)市場需求和重構(gòu)時(shí)間,選擇能切入的時(shí)機(jī)。比較有效的一個(gè)方法是Small Step重構(gòu),把重構(gòu)任務(wù)進(jìn)行拆解,切分到一個(gè)個(gè)迭代中增量完成。

遙遙無期的重構(gòu),由于項(xiàng)目看不到短期收益,容易動(dòng)搖支持重構(gòu)的決心;另外,在重構(gòu)期間,可能還不斷有新功能加入,為了做到可以替代原有產(chǎn)品,在重構(gòu)同時(shí),還需要不斷追趕這些功能,巨大的壓力,容易使團(tuán)隊(duì)身心疲憊。

  1. 代碼度量數(shù)據(jù)

平均圈復(fù)雜度、函數(shù)平均行數(shù)、代碼總行數(shù)、重復(fù)度等代碼度量,可以作為是否進(jìn)行重構(gòu)的參考,也是預(yù)估重構(gòu)周期的一個(gè)重要指標(biāo)。另外,重構(gòu)過程中,在CI部署代碼度量檢查,可以看到代碼復(fù)雜度不斷下降,提升堅(jiān)持重構(gòu)的信心。

  1. 自動(dòng)化測試包圍情況

保證重構(gòu)“不改變軟件可觀察行為”最有效的舉措,就是待重構(gòu)代碼已經(jīng)有大量自動(dòng)化測試用例包圍??紤]如下情況:

  • 測試用例最好是基于業(yè)務(wù)進(jìn)行拆分,并且覆蓋場景比較全面
  • 測試框架支持不同平臺(tái),可以減少重構(gòu)對(duì)平臺(tái)環(huán)境的依賴,自由選擇
  • 如果已有測試用例執(zhí)行速度較快,可以保證重構(gòu)有更好的節(jié)奏感。

如果測試用例覆蓋場景較少,不推薦補(bǔ)充完所有場景測試用例后再進(jìn)行重構(gòu)。一個(gè)推薦的做法是,按照重構(gòu)計(jì)劃,先補(bǔ)充某個(gè)場景用例,然后對(duì)其進(jìn)行重構(gòu),交付后繼續(xù)進(jìn)行下一個(gè)場景,循環(huán)迭代,直到所有場景都完成。

另外,在CI中部署分支覆蓋率監(jiān)控工具,可以感知到分支覆蓋情況逐漸變好,在代碼重構(gòu)完成同時(shí),也交付了一份自動(dòng)化測試用例(當(dāng)然,分支覆蓋率僅能保證分支被跑到,并不能保證邏輯正確)。

遺留代碼重構(gòu)決策表(Excel版)

refactor-decision-list

下載地址:https://github.com/liyongshun/refactor/blob/master/refactor_decision_list.xlsx

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 背景 《重構(gòu)》誕生至今有近17個(gè)年頭了,日常開發(fā)中大家談到重構(gòu),要么非常隨意,認(rèn)為重構(gòu)就是改代碼;要么非常謹(jǐn)慎,把...
    李永順閱讀 3,593評(píng)論 0 18
  • 最近為項(xiàng)目組整理相關(guān)項(xiàng)目相關(guān)的規(guī)范文檔,并整理了項(xiàng)目中,代碼中的一些問題,識(shí)別項(xiàng)目代碼中的一些可以優(yōu)化的代碼...
    Android開發(fā)_Hua閱讀 856評(píng)論 0 1
  • 如果你在軟件行業(yè)工作足夠長的時(shí)間,遲早你都將面臨一個(gè)棘手的問題:修復(fù)遺留的代碼庫。本文所提出的并不是唯一可行的方法...
    observerb閱讀 1,143評(píng)論 0 1
  • 一個(gè)故事 在進(jìn)入這個(gè)話題前,我們先講一個(gè)故事。 開發(fā)同學(xué)從另一個(gè)團(tuán)隊(duì)接手了新的系統(tǒng)有一段時(shí)間了,但是平時(shí)都是加全新...
    JayWu的架構(gòu)筆記閱讀 750評(píng)論 0 0
  • 表情是什么,我認(rèn)為表情就是表現(xiàn)出來的情緒。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了,難過就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 129,670評(píng)論 2 7

友情鏈接更多精彩內(nèi)容