譯者 | 劉濤
審校 | 重樓
目錄
重構(gòu)概述
重構(gòu)前期準(zhǔn)備
- 爭(zhēng)取管理層支持
- 通過(guò)自動(dòng)化測(cè)試確保安全保障
- 識(shí)別高風(fēng)險(xiǎn)區(qū)域
- 設(shè)定明確的重構(gòu)目標(biāo)
復(fù)雜代碼庫(kù)的重構(gòu)技巧
- 識(shí)別并隔離問(wèn)題區(qū)域
- 漸進(jìn)式重構(gòu)與大爆炸式重構(gòu)
- 拆分單體代碼
- 確保向后兼容性
- 處理依賴關(guān)系與緊密耦合
- 測(cè)試策略(自信地安全重構(gòu))
- 在不降低性能的前提下進(jìn)行重構(gòu)
- 使用人工智能工具自動(dòng)化代碼審查
總結(jié)
一、重構(gòu)概述
重構(gòu)是一種對(duì)代碼開(kāi)展持續(xù)優(yōu)化的重要手段,其核心目標(biāo)在于降低代碼的復(fù)雜程度,削減技術(shù)債務(wù)。通過(guò)不斷完善代碼庫(kù)來(lái)解決項(xiàng)目推進(jìn)過(guò)程中出現(xiàn)的代碼結(jié)構(gòu)惡化問(wèn)題,它能將雜亂無(wú)章或低效的代碼轉(zhuǎn)變?yōu)榻Y(jié)構(gòu)良好、便于維護(hù)的解決方案。
二、重構(gòu)前期準(zhǔn)備
在著手進(jìn)行代碼重構(gòu)之前,搭建一個(gè)堅(jiān)實(shí)穩(wěn)固的基礎(chǔ)有著舉足輕重的意義。這不僅有助于規(guī)范重構(gòu)流程,確保各項(xiàng)工作有序開(kāi)展,還能促進(jìn)團(tuán)隊(duì)成員間的高效協(xié)作與溝通,使全體成員對(duì)目標(biāo)和任務(wù)達(dá)成共識(shí),避免因信息不一致或方向偏差阻礙項(xiàng)目進(jìn)展。
1.爭(zhēng)取管理層支持
若能將代碼重構(gòu)工作與核心業(yè)務(wù)成果、產(chǎn)品上市周期的有效縮短、系統(tǒng)宕機(jī)概率的顯著降低以及開(kāi)展新業(yè)務(wù)計(jì)劃的能力進(jìn)行深度關(guān)聯(lián),清晰展示出重構(gòu)工作在這些關(guān)鍵層面所具備的潛在價(jià)值,管理層通常會(huì)更傾向于為重構(gòu)工作投入所需資源。
2.通過(guò)自動(dòng)化測(cè)試確保安全保障
在進(jìn)行代碼重構(gòu)時(shí),測(cè)試堪稱保障重構(gòu)安全的關(guān)鍵防線。在對(duì)某個(gè)組件進(jìn)行修改之前,需要圍繞它編寫(xiě)特征測(cè)試。
# example: characterization test for a legacy function
def legacy_calculate_discount(price, rate):
# ... complex logic you don't fully understand yet ...
return price * (1 - rate/100) if rate < 100 else 0
def test_legacy_calculate_discount():
# capture existing behavior
assert legacy_calculate_discount(100, 10) == 90
assert legacy_calculate_discount(50, 200) == 0
測(cè)試在重構(gòu)中記錄運(yùn)行狀態(tài),單元、集成和端到端測(cè)試可驗(yàn)證重構(gòu)是否破壞原有功能。自動(dòng)化測(cè)試至關(guān)重要,搭建持續(xù)集成管道,使每次代碼變更觸發(fā)測(cè)試,能快速反饋,避免引入回歸問(wèn)題。
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with: python-version: '3.10'
- run: pip install -r requirements.txt
- run: pytest --maxfail=1 --disable-warnings -q
3.識(shí)別高風(fēng)險(xiǎn)區(qū)域
重構(gòu)的首要步驟是明確重構(gòu)對(duì)象。高風(fēng)險(xiǎn)區(qū)域指代碼中易引發(fā)漏洞或阻礙開(kāi)發(fā)進(jìn)度的部分,常見(jiàn)表現(xiàn)有方法冗長(zhǎng)、類體量過(guò)大、代碼重復(fù)以及復(fù)雜的條件邏輯。
借助靜態(tài)分析工具能夠自動(dòng)識(shí)別此類問(wèn)題。如SonarQube能標(biāo)記增加技術(shù)債務(wù)的代碼缺陷。使用同類工具,能夠生成代碼復(fù)雜度(如圈復(fù)雜度指標(biāo))報(bào)告,精準(zhǔn)定位代碼庫(kù)中需重點(diǎn)關(guān)注的區(qū)域。
4.設(shè)定明確的重構(gòu)目標(biāo)
重構(gòu)代碼前,需明確具體且可量化的目標(biāo),如降低類規(guī)模或函數(shù)圈復(fù)雜度、提升單元測(cè)試覆蓋率等。每個(gè)目標(biāo)對(duì)應(yīng)可衡量結(jié)果,如精簡(jiǎn)方法、減少條件語(yǔ)句等,既能指引重構(gòu)計(jì)劃,又方便驗(yàn)證成果。
三、復(fù)雜代碼庫(kù)的重構(gòu)技巧
1.識(shí)別并隔離問(wèn)題區(qū)域
最高效的重構(gòu)工作通常聚焦于“問(wèn)題區(qū)域”,即代碼庫(kù)中復(fù)雜度高、易出錯(cuò),或成為開(kāi)發(fā)與性能瓶頸的部分。精準(zhǔn)識(shí)別這些區(qū)域后,需進(jìn)行隔離以便安全重構(gòu),可采用以下策略:
- 打破依賴關(guān)系(創(chuàng)建接縫):有效利用遺留代碼。如 PaymentService 與 StripeGateway 緊密耦合的情況。
- 添加抽象層:在舊代碼前添加抽象層(如接口或代理),后面新舊代碼實(shí)現(xiàn),逐漸將舊實(shí)現(xiàn)遷移到新實(shí)現(xiàn),可通過(guò)配置或功能標(biāo)志切換。
2.漸進(jìn)式重構(gòu)與大爆炸式重構(gòu)
重構(gòu)時(shí)需在漸進(jìn)式重構(gòu)和“大爆炸”式重構(gòu)中做戰(zhàn)略選擇。多數(shù)情況下,漸進(jìn)式方法更優(yōu),但特定場(chǎng)景下也會(huì)考慮大爆炸式重構(gòu)。
# before: one large function with multiple responsibilities
def process_order(order):
validate(order)
apply_discount(order)
save_to_db(order)
send_confirmation(order)
log_metrics(order)
update_loyalty_points(order)
# potentially more steps
# after: refactored incrementally into clearer, smaller units
def process_order(order):
validate(order)
apply_discount(order)
persist_and_notify(order)
def persist_and_notify(order):
save_to_db(order)
send_confirmation(order)
log_metrics(order)
update_loyalty_points(order)
漸進(jìn)式重構(gòu)
漸進(jìn)式重構(gòu)是隨時(shí)間推移進(jìn)行微小、易管理的更改,而非一次性大規(guī)模整改。其優(yōu)勢(shì)在于降低風(fēng)險(xiǎn),出錯(cuò)概率低且后續(xù)易定位修復(fù)。
大爆炸式重構(gòu)
這是“推倒重來(lái)”的方法,停止添加新功能,凍結(jié)代碼一段時(shí)間,投入大量精力重新設(shè)計(jì)或重寫(xiě)系統(tǒng)大部分甚至全部?jī)?nèi)容,目標(biāo)是打造全新整潔的系統(tǒng)。
3.拆分單體代碼
許多復(fù)雜代碼庫(kù)最初是單一的單體應(yīng)用,即一個(gè)可部署的代碼項(xiàng)目或一組緊密耦合、統(tǒng)一維護(hù)發(fā)布的模塊。隨著時(shí)間推移,單體應(yīng)用變得難以管控,因此需將單體應(yīng)用模塊化或拆分為更易管理的部分。
# define the interfaceclass PaymentProcessor:
def charge(self, amount): ...
# old implementationclass LegacyProcessor(PaymentProcessor):
def charge(self, amount):
# original code
# new implementation behind a feature flagclass NewProcessor(PaymentProcessor):
def charge(self, amount):
# cleaner code
def get_processor():
if config.feature_new_payment:
return NewProcessor()
return LegacyProcessor()
# usage remains the same
processor = get_processor()
processor.charge(100)
模塊化策略
拆分單體應(yīng)用可采用以下策略:
- 分層分離:強(qiáng)化邏輯層邊界,將用戶界面、業(yè)務(wù)邏輯、數(shù)據(jù)訪問(wèn)代碼分開(kāi),按層組織代碼以限制變更連鎖反應(yīng)。
- 基于領(lǐng)域的模塊化:按業(yè)務(wù)領(lǐng)域或功能區(qū)域拆分,如電商單體應(yīng)用拆分為賬戶、訂單等模塊,減少模塊間內(nèi)部信息依賴,通過(guò)清晰 API 交互。
- 微服務(wù)或服務(wù)提?。簩误w應(yīng)用拆分為通過(guò) API 通信的獨(dú)立微服務(wù),提升獨(dú)立部署和可擴(kuò)展性,可循序漸進(jìn)先提取一項(xiàng)功能為單獨(dú)服務(wù),再逐步處理其他模塊。
- 模塊化單體架構(gòu):將單個(gè)應(yīng)用構(gòu)建為通過(guò)明確接口通信的模塊,類似內(nèi)部微服務(wù),可獲微服務(wù)優(yōu)勢(shì),避免運(yùn)維復(fù)雜性。
- 區(qū)分共享工具與獨(dú)立組件:拆分時(shí)將廣泛共享的代碼整合到庫(kù)或服務(wù)中。
4.確保向后兼容性
大規(guī)模重構(gòu)時(shí),關(guān)鍵要確保重構(gòu)后依賴代碼的系統(tǒng)、模塊或客戶端能否正常運(yùn)行。若代碼庫(kù)涉及提供公共API、持久化數(shù)據(jù)或配置文件等內(nèi)容時(shí),向后兼容性尤為重要。以下是維持向后兼容性的策略:
- 函數(shù)重構(gòu):對(duì)于廣泛使用的函數(shù)(如 send_email)進(jìn)行重構(gòu)時(shí),需保持其公共 API 不變,將調(diào)用委托給新的內(nèi)部函數(shù);引入改進(jìn)后的新版本時(shí),在文檔中標(biāo)記舊版本為已棄用,同時(shí)確保舊版本在內(nèi)部調(diào)用新版本,為其他團(tuán)隊(duì)留出遷移時(shí)間。
- 編寫(xiě)適配器或兼容層:重構(gòu)底層數(shù)據(jù)模型但存在舊配置文件時(shí),編寫(xiě)適配器程序?qū)⑴f配置文件格式轉(zhuǎn)換為新格式,使舊數(shù)據(jù)仍能正常使用,實(shí)現(xiàn)數(shù)據(jù)的平滑過(guò)渡與兼容。
- 進(jìn)行兼容性測(cè)試:納入確保向后兼容性的測(cè)試,如針對(duì)公共 API保留使用舊版本定義的交互規(guī)則,開(kāi)展測(cè)試并在重構(gòu)后運(yùn)行。
5.處理依賴關(guān)系與緊密耦合
重構(gòu)大型代碼庫(kù)時(shí),復(fù)雜系統(tǒng)常存在緊密耦合問(wèn)題,如模塊間相互依賴細(xì)節(jié)、廣泛使用全局變量或單例模式,任何一處改動(dòng)都有可能影響大片代碼。
降低耦合度是重構(gòu)的重要目標(biāo),可使代碼更具模塊化,便于獨(dú)立理解、測(cè)試和修改。以下是降低耦合度的策略:
- 引入接口或抽象層:在組件間引入接口是有效的解耦方法。例如,讓直接查詢數(shù)據(jù)庫(kù)的類使用接口,使類不依賴具體數(shù)據(jù)獲取方式,遵循依賴倒置原則。
# before: direct instantiation
class OrderService:
def __init__(self):
self.repo = OrderRepository()
# after: inject dependency
class OrderService:
def __init__(self, repo):
self.repo = repo
# wiring up in application startup
repo = OrderRepository(db_conn)
service = OrderService(repo)
- 使用依賴注入:完成接口配置后,可通過(guò)依賴注入提供具體實(shí)現(xiàn)。
- 外觀模式或包裝服務(wù):若特定子系統(tǒng)與其他子系統(tǒng)糾纏復(fù)雜時(shí),可引入外觀模式和包裝服務(wù)以優(yōu)化。
- 逐步替換(并行運(yùn)行):若需用新實(shí)現(xiàn)替換特定組件,可暫時(shí)并行運(yùn)行新舊組件。例如,對(duì)于需重構(gòu)的復(fù)雜模塊,可保留舊代碼以處理遺留調(diào)用,同時(shí)引導(dǎo)新調(diào)用至新模塊。
6.測(cè)試策略(自信地安全重構(gòu))
穩(wěn)健的測(cè)試策略能為大規(guī)模重構(gòu)提供信心,確保出現(xiàn)問(wèn)題時(shí)可迅速察覺(jué)。在大規(guī)模重構(gòu)中,可按以下方式開(kāi)展測(cè)試:
- 通過(guò)回歸測(cè)試建立基線:重構(gòu)特定組件前,需確保有覆蓋其當(dāng)前行為的測(cè)試。若代碼庫(kù)測(cè)試套件不完善,應(yīng)優(yōu)先編寫(xiě)特性測(cè)試。
- 持續(xù)集成:建議將測(cè)試集成到持續(xù)集成管道,每次提交或合并代碼時(shí)運(yùn)行測(cè)試,以便及時(shí)捕獲重構(gòu)過(guò)程中引入的錯(cuò)誤,縮短反饋周期。
- 金絲雀發(fā)布測(cè)試:金絲雀發(fā)布測(cè)試是先將變更部署到部分用戶或服務(wù)器,觀察后逐步擴(kuò)大范圍,有助于發(fā)現(xiàn)測(cè)試遺漏的問(wèn)題。若表現(xiàn)良好則全面推廣,反之則迅速回滾,減小影響范圍。
- 性能與負(fù)載測(cè)試:若性能是關(guān)注點(diǎn),應(yīng)在測(cè)試策略中加入性能測(cè)試,可在預(yù)發(fā)布環(huán)境進(jìn)行。若性能顯著下降,需重新考慮重構(gòu)方法或優(yōu)化新代碼。
- 測(cè)試舊代碼:處理未經(jīng)測(cè)試的舊代碼時(shí),應(yīng)優(yōu)先覆蓋部分內(nèi)容,可采用審批測(cè)試等技術(shù),以此為重構(gòu)指引方向。總之,強(qiáng)大的測(cè)試策略對(duì)重構(gòu)復(fù)雜系統(tǒng)至關(guān)重要,它是安全保障和早期預(yù)警系統(tǒng),能確保重構(gòu)不破壞關(guān)鍵內(nèi)容。
7.在不降低性能的前提下進(jìn)行重構(gòu)
重構(gòu)代碼不會(huì)改變算法和數(shù)據(jù)結(jié)構(gòu),因此理論上并不會(huì)影響系統(tǒng)性能。但實(shí)際操作中可能會(huì)無(wú)意間影響性能,如占用更多內(nèi)存或移除關(guān)鍵緩存機(jī)制。資深工程師重構(gòu)時(shí)需關(guān)注性能敏感部分,避免性能倒退,以下是兼顧性能的方法:
- 確定關(guān)鍵路徑:不同代碼對(duì)性能影響不同,重構(gòu)性能關(guān)鍵代碼時(shí)要重新測(cè)量性能;非關(guān)鍵部分操作空間更大。
- 使用分析工具:用性能分析器在重構(gòu)前后測(cè)量代碼,對(duì)比性能表現(xiàn),及時(shí)發(fā)現(xiàn)性能問(wèn)題。
- 提升性能機(jī)會(huì):重構(gòu)也可提升性能,如重構(gòu)重復(fù)代碼以使用更好的緩存機(jī)制,重構(gòu)時(shí)留意此類機(jī)會(huì)。
8.使用人工智能工具自動(dòng)化代碼審查
代碼重構(gòu)是個(gè)持續(xù)過(guò)程,人工智能代碼審查工具可推行整潔代碼標(biāo)準(zhǔn),提前發(fā)現(xiàn)代碼問(wèn)題,減少重復(fù)工作,讓工程師專注于架構(gòu)或特定領(lǐng)域難題。
CodeRabbit作為一款強(qiáng)大的人工智能驅(qū)動(dòng)審查平臺(tái),旨在減半審查時(shí)間與漏洞數(shù)量,其工作原理及對(duì)重構(gòu)流程的優(yōu)化作用如下:
智能情境反饋
CodeRabbit利用先進(jìn)語(yǔ)言模型與靜態(tài)分析技術(shù),逐行分析拉取請(qǐng)求,在人工介入前就能標(biāo)記潛在漏洞、不良實(shí)踐與風(fēng)格問(wèn)題。
多元實(shí)用功能
- 摘要生成與一鍵修復(fù):為大型拉取請(qǐng)求生成摘要,快速應(yīng)用簡(jiǎn)單修復(fù)方案。
- 實(shí)時(shí)協(xié)作與智能交互:通過(guò)與人工智能聊天獲取解釋、替代代碼與即時(shí)反饋。
- 主流平臺(tái)集成:支持 GitHub、GitLab 和 Azure DevOps,實(shí)現(xiàn)拉取請(qǐng)求無(wú)縫掃描。
此外,CodeRabbit 還在VS Code 提供免費(fèi)審查功能,借助該擴(kuò)展,開(kāi)發(fā)者可在編輯器內(nèi)直接獲得先進(jìn)審查服務(wù),節(jié)省時(shí)間、捕捉更多漏洞,推動(dòng)代碼重構(gòu)。
總結(jié)
重構(gòu)應(yīng)成為持續(xù)的過(guò)程。把相關(guān)實(shí)踐融入日常開(kāi)發(fā),如每個(gè)沖刺周期預(yù)留時(shí)間重構(gòu),或在處理代碼時(shí)適時(shí)開(kāi)展,可避免代碼庫(kù)質(zhì)量下滑。單次小的重構(gòu)不必過(guò)于復(fù)雜,但其積累效應(yīng)顯著。
譯者介紹
劉濤,51CTO社區(qū)編輯,某大型央企系統(tǒng)上線檢測(cè)管控負(fù)責(zé)人。
原文標(biāo)題:How to Refactor Complex Codebases – A Practical Guide for Devs,作者:Ankur Tyagi