升級(jí)遺留代碼的優(yōu)秀實(shí)踐
在傳統(tǒng)企業(yè)甚至互聯(lián)網(wǎng)企業(yè)中往往存在大量的遺留代碼,這些遺留代碼大多都能夠正常工作,有的可能還運(yùn)行著關(guān)鍵業(yè)務(wù)或者持有核心數(shù)據(jù)。但是,大部分遺留代碼通常經(jīng)常存在技術(shù)陳舊、代碼復(fù)雜、難以修改等特點(diǎn)。隨著時(shí)間的推移,遺留代碼的維護(hù)和管理的成本越來(lái)越大。在全面轉(zhuǎn)型微服務(wù)的今天,這些遺留代碼該如何處理呢?Tomasz Kania-Orzeł 為我們闡述了升級(jí)遺留代碼的最佳實(shí)踐,我相信,這篇文章對(duì)于擁有大量遺留代碼的企業(yè) / 組織很有借鑒意義。
“我在 Ruby on Rails 上有一款可以追溯到 2011 年的應(yīng)用,在過(guò)去五年來(lái)沒(méi)有添加任何新功能。而且它現(xiàn)在運(yùn)行死慢死慢的,隨著我們用戶群不斷地增長(zhǎng),它已經(jīng)幾乎不能提供服務(wù)了。您能幫我們解決這個(gè)問(wèn)題嗎?”
這是我在 Monterail 的客戶那兒遇到的最常見(jiàn)的情景之一。這種難以維護(hù)且存在安全漏洞的遺留代碼,對(duì)于必須使用它的企業(yè),以及必須處理它的開(kāi)發(fā)人員(比如我們)來(lái)說(shuō),真不啻噩夢(mèng)一場(chǎng)。在我擔(dān)任軟件工程師十年左右的時(shí)間里,有過(guò)很多機(jī)會(huì)讓我得以觀察一些開(kāi)發(fā)人員為了更新 Web 應(yīng)用程序中的遺留代碼而進(jìn)行的技術(shù)轉(zhuǎn)變,這些技術(shù)轉(zhuǎn)變有成功,也有失敗。例如,這可能意味著,從某個(gè)框架的版本 2 升級(jí)為版本 6,或者從 Ruby 變更為 Python,或者從單體應(yīng)用轉(zhuǎn)變?yōu)槲⒎?wù)架構(gòu),或者從手工構(gòu)建更改為持續(xù)交付。為了完成一次無(wú)痛(或至少不那么痛苦)更新,你必須確定進(jìn)行更改是否有必要,確定最合適自己的方法并承諾長(zhǎng)期踐行。
1. 應(yīng)該何時(shí)進(jìn)行變更?
糟糕的性能是做出技術(shù)變更的原因。另一個(gè)原因就是你所使用的技術(shù)的普及程度逐漸或突然下降了。畢竟,如果市場(chǎng)上能夠支持你工作的開(kāi)發(fā)人員越來(lái)越少,那么你的技術(shù)存在被封閉的風(fēng)險(xiǎn)就越來(lái)越大。有些人早在 2010 年就用 Backbone 構(gòu)建了他們的應(yīng)用程序,如今卻在努力解決“模型 - 視圖 - 控制器”的問(wèn)題,而其他人都在使用基于組件的框架,如 React 或 Vue 等。如果你選擇的框架正在失去積極的支持,那么風(fēng)險(xiǎn)就會(huì)更大。還記得 AngularJS 嗎?2018 年 7 月,它就進(jìn)入了長(zhǎng)期支持階段,這意味著 Google 不會(huì)再合并新的功能或修補(bǔ)程序,哪怕是一個(gè)微小的突破性改動(dòng)。
譯注:“模型 - 視圖 - 控制器”(Model–view–controller,MVC),是是軟件工程中的一種軟件架構(gòu)模式,把軟件系統(tǒng)分為三個(gè)基本部分:模型(Model)、視圖(View)和控制器(Controller)。MVC 最早由 Trygve Reenskaug 在 1978 年提出,是 Xerox PARC 在 20 世紀(jì) 80 年代為程序語(yǔ)言 Smalltalk 發(fā)明的一種軟件架構(gòu)。MVC 的目的是實(shí)現(xiàn)一種動(dòng)態(tài)的程式設(shè)計(jì),使后續(xù)對(duì)程序的修改和擴(kuò)展簡(jiǎn)化,并且使程序某一部分的重復(fù)利用成為可能。除此之外,MVC 通過(guò)對(duì)復(fù)雜度的簡(jiǎn)化,使程序結(jié)構(gòu)更加直觀。
當(dāng)你的技術(shù)在人力或機(jī)器成本方面過(guò)于低效、過(guò)于昂貴,這不僅是發(fā)起技術(shù)變更的一個(gè)好機(jī)會(huì),而且也可能是你在技術(shù)變得不可挽回之前修復(fù)它的最后機(jī)會(huì)。你永遠(yuǎn)不會(huì)想達(dá)到這樣的地步:創(chuàng)建一個(gè)新功能是完全不可行的。歐洲電子商務(wù)公司 Zalando 正努力快速擴(kuò)展其單體 PHP 應(yīng)用程序,卻 無(wú)法以快速或高效的方式提供新功能。這促使他們?cè)?2016 年從單體架構(gòu)應(yīng)用轉(zhuǎn)向微服務(wù),使不同的團(tuán)隊(duì)能夠以更快的速度交付功能。
2. 應(yīng)該如何進(jìn)行變更
一旦確定你正在開(kāi)發(fā)的產(chǎn)品需要升級(jí),那么就是時(shí)候?qū)ψ兏较蜃龀雒髦堑臎Q定了。代碼一旦編寫就會(huì)成為遺留代碼, 沒(méi)有人能保證你當(dāng)時(shí)編寫所用的技術(shù)在未來(lái)不會(huì)失去支持或變得過(guò)時(shí)。(安息吧,AngularJS。)因此,變更應(yīng)該支持未來(lái)的靈活性。以下是一些選項(xiàng):
選項(xiàng)一:大爆炸式的重寫
第一個(gè)也是最明顯的選項(xiàng),就是大爆炸式的重寫:從頭開(kāi)始更改代碼庫(kù),并在一次轉(zhuǎn)換中切換所有用戶。但是,完全重寫是非常耗時(shí)的,而且必然也會(huì)產(chǎn)生相當(dāng)巨大的成本。你也有可能最終得到的是一款在幾個(gè)月甚至幾年內(nèi)都不適合發(fā)布的應(yīng)用程序,并且在這個(gè)過(guò)程結(jié)束之前,你都無(wú)法看到最終結(jié)果。另外,應(yīng)用程序越大,開(kāi)發(fā)者在重寫過(guò)程中提供維護(hù)和添加新功能的難度就越大。如果有文檔和技術(shù)知識(shí)的話,向大型代碼庫(kù)添加新功能就像在公園里散步一樣簡(jiǎn)單。若沒(méi)有這些的話,要做到這些,真的很難。
選項(xiàng)二:鳳凰涅槃
第二個(gè)選項(xiàng)是在現(xiàn)有代碼庫(kù)中添加使用新技術(shù)構(gòu)建的新功能。理想情況下,你不應(yīng)該觸及舊技術(shù),而應(yīng)該將所有新功能分離開(kāi)來(lái)。但不幸的是,這樣的原始結(jié)果相當(dāng)罕見(jiàn):新功能幾乎總是需要與舊功能集成。這也需要一個(gè)詳細(xì)的計(jì)劃,因?yàn)槭虑楹軓?fù)雜,沒(méi)有周密計(jì)劃的話很難做好。在復(fù)雜的架構(gòu)中創(chuàng)建新的組件,需要大量關(guān)于遺留應(yīng)用程序中組件運(yùn)行情況的信息。你需要一個(gè)廣闊的視角,從新舊兩個(gè)角度看待這項(xiàng)技術(shù)。
使用單體應(yīng)用程序,你可以在單獨(dú)部署的新代碼庫(kù)中創(chuàng)建新功能,同時(shí)使用與新代碼和遺留代碼交互的單個(gè)數(shù)據(jù)庫(kù)來(lái)存儲(chǔ)數(shù)據(jù)。這個(gè)解決方案看起來(lái)很簡(jiǎn)單,但它能否取得長(zhǎng)期成功,要取決于你的承諾是否堅(jiān)如磐石。(特別是當(dāng)你的整體系統(tǒng)受到機(jī)器性能或并發(fā)性問(wèn)題的影響時(shí),就需要考慮這些問(wèn)題。)例如,如果你的單體應(yīng)用程序開(kāi)始獲得更多的用戶,那么,單個(gè)數(shù)據(jù)庫(kù)可能會(huì)成為瓶頸。(另一方面,云端中的數(shù)據(jù)庫(kù)可以擴(kuò)展。)由于原始代碼庫(kù)的長(zhǎng)期脆弱性,特別是考慮到潛在的安全漏洞或 bug,這種架構(gòu)并不能持久。實(shí)際上,讓過(guò)時(shí)的代碼保持原樣就意味著你在等待它最終永遠(yuǎn)失敗。
選項(xiàng)三:混合方法
重寫整個(gè)代碼庫(kù)是一個(gè)極端的想法,而且往往考慮不周。在舊代碼庫(kù)上添加新功能更可行,但會(huì)帶來(lái)嚴(yán)重的副作用,例如,如果你的遺留代碼是基于較舊的框架版本,那么就會(huì)出現(xiàn)安全問(wèn)題。那么,還有什么事情是你可以做的,而且不那么昂貴,或者不那么危險(xiǎn)的? 你還有其他選擇嗎?
我推薦一種混合方法。這一選項(xiàng),就像大爆炸式重寫一樣,也需要對(duì)整個(gè)舊代碼進(jìn)行變更。但與第一種選項(xiàng)不同的是,重寫應(yīng)該是在一段時(shí)間內(nèi)展開(kāi)(比如說(shuō)幾年),以最大限度地減少技術(shù)債務(wù)和財(cái)務(wù)成本。這種漸進(jìn)式方法將基于你的愿景,這一點(diǎn)至關(guān)重要。你需要知道首先要改變什么:有些功能是核心的,對(duì)業(yè)務(wù)至關(guān)重要,而其他功能則更多的是扮演輔助角色。有了明確的目標(biāo),在多個(gè)層面上工作就會(huì)變得更容易。例如,你是否仍然計(jì)劃在這個(gè)轉(zhuǎn)換過(guò)程中發(fā)布一項(xiàng)新功能?如果是這樣的話,你就需要在計(jì)劃中考慮到這一點(diǎn)。如果沒(méi)有清晰的路線圖,你的代碼最終將會(huì)變得一團(tuán)糟,這將會(huì)使開(kāi)發(fā)者更難理解。
對(duì)我來(lái)說(shuō),這種方法關(guān)乎未來(lái)和可擴(kuò)展性:而且它最容易使用微服務(wù)來(lái)實(shí)現(xiàn)。假設(shè)你的遺留代碼庫(kù)是新的微服務(wù)生態(tài)系統(tǒng)中的一個(gè)元素。當(dāng)然,它太過(guò)于龐大,過(guò)于復(fù)雜,不可能是真正的微服務(wù),但它可以像微服務(wù)一樣與新功能進(jìn)行通信。為了處理這種安排,你需要?jiǎng)?chuàng)建 API 或“橋”這樣的接口,以允許遺留代碼與新技術(shù)進(jìn)行通訊。當(dāng)你以單獨(dú)的微服務(wù)形式添加新功能時(shí),它們將會(huì)一點(diǎn)一點(diǎn)地吞噬遺留代碼的業(yè)務(wù)邏輯。雖然你可以通過(guò)向遺留的單體應(yīng)用程序添加新功能來(lái)實(shí)現(xiàn)類似的功能,但此舉可能會(huì)造成技術(shù)債務(wù),并失去靈活性。
幾乎所有的解決方案都有副作用,包括這種方法。但是我們需要知道如何將副作用最小化。對(duì)于 Web 應(yīng)用的前端,反向代理可以緩和這一變更帶來(lái)的副作用。使用這種方法,你甚至可以在不觸及遺留軟件的情況下,替換 Web 應(yīng)用中為單個(gè) URL 提供服務(wù)的邏輯。但這種技術(shù)有其自身的要求,例如,如果你有用戶登錄的話,就應(yīng)該維護(hù)頁(yè)面之間的狀態(tài)。我們始終需要存儲(chǔ)狀態(tài),但在這個(gè)解決方案中,我們需要在兩個(gè)應(yīng)用之間移動(dòng)或共享狀態(tài)。這很難維護(hù),但你還是可以得到更具彈性的基礎(chǔ)設(shè)施。
更復(fù)雜的更改需要對(duì)基礎(chǔ)設(shè)施進(jìn)行改進(jìn),比如,創(chuàng)建一個(gè)前端服務(wù)器層,你可以從其中呈現(xiàn)來(lái)自不同源的應(yīng)用程序片段,如上面的微服務(wù)示例?;?XML 的標(biāo)記語(yǔ)言 ESI(Edge Sides Includes)可能適合這項(xiàng)任務(wù),而 Varish 或 Nginx 然后,為了確保你的應(yīng)用在用戶群增長(zhǎng)時(shí)能夠保持性能,請(qǐng)創(chuàng)建負(fù)載均衡器和基于上下文的獨(dú)立數(shù)據(jù)庫(kù),這些數(shù)據(jù)庫(kù)在微服務(wù)或宏服務(wù)中單獨(dú)使用。
創(chuàng)造一個(gè)能夠支持這種安排的靈活環(huán)境也是一個(gè)挑戰(zhàn)。轉(zhuǎn)移到微服務(wù)時(shí),你只需在基礎(chǔ)設(shè)施上投資一次,但你將需要進(jìn)一步支持這種架構(gòu)的維護(hù)。盡管如此,它可能仍然比重寫所有代碼要便宜得多。
如果你的主要目標(biāo)是創(chuàng)建一個(gè)易于維護(hù)的生態(tài)系統(tǒng)(而不是關(guān)注性能第一,維護(hù)第二),你還需要在開(kāi)發(fā)過(guò)程中確定系統(tǒng)的關(guān)鍵元素,并創(chuàng)建路線圖來(lái)對(duì)它們進(jìn)行更改。在這個(gè)過(guò)程中引入一些持續(xù)集成和部署的魔法,其中,CI 和 CD 流程可以在沒(méi)有 QA 或開(kāi)發(fā)人員的幫助就能自動(dòng)進(jìn)行,然后你最終將得到一個(gè)結(jié)構(gòu)清晰、易于修改和調(diào)整的成熟軟件。
當(dāng)然,這種混合方法并不是世界上唯一可行或正在使用的選項(xiàng)。但是,代碼庫(kù)的增量更改最終導(dǎo)致了完全重寫,你得以能夠使用工作代碼,從而使業(yè)務(wù)保持安全,同時(shí),微服務(wù)使不同團(tuán)隊(duì)能夠獨(dú)立地交付新的和不同的功能,提供了一個(gè)為長(zhǎng)期使用而設(shè)計(jì)的過(guò)程和架構(gòu)。
3. 要做持久的更改需要什么?
你可能會(huì)看著我的首選解決方案,然后心想,“嗯,這有點(diǎn)過(guò)于工程化了”,或者“我沒(méi)有一個(gè)團(tuán)隊(duì)能勝任這種工作”,或者“這對(duì)我的平臺(tái)來(lái)說(shuō)太過(guò)于復(fù)雜”,或者甚至“這不是純粹的微服務(wù)架構(gòu)!”我并不反對(duì)你這些想法,但我確實(shí)認(rèn)為,升級(jí)你的技術(shù)將會(huì)迫使你作長(zhǎng)遠(yuǎn)考慮。
我提供的并不是快速解決方案。相反,混合方法為你提供了基于工作基礎(chǔ)之上的新技術(shù)。通過(guò)逐步轉(zhuǎn)向微服務(wù),增量更改允許你輕松地更新應(yīng)用程序,并利用最新的框架,所有這些都不會(huì)迫使你在可靠性上作出妥協(xié)。那么,你準(zhǔn)備好重新構(gòu)建你的軟件了嗎?
作者介紹
Tomasz Kania-Orzel,是一名經(jīng)驗(yàn)豐富的軟件和 DevOps 工程師,最喜歡研究前端技術(shù)。目前在 Monterail 擔(dān)任技術(shù)主管,領(lǐng)導(dǎo)一支由 75 名開(kāi)發(fā)人員組成的團(tuán)隊(duì)。















 
 
 







 
 
 
 