Categories
程式開發

接手了嚴重過時的軟件,到底是該逐步重構還是摧毀重寫呢?


有一個應用程序充斥著技術債,嚴重的過時了,或者只是對用戶服務不足,因此,我們需要了解我們的最佳選擇是什麼——是繼續艱難地探索並逐步進行重構更有意義,還是把它全部摧毀並從頭開始重寫更有意義呢? 這就是我們將在本文中探討的基本難題。 所以讓我們開始吧……

但是沒有那麼快! 在我們進一步研究之前,需要解決一個大家“避而不談”的問題,即:對於任何需要改進的遺留應用程序,下一步要做什麼並不是一個這樣或那樣就可以了的簡單決定。我們通常會將我們的選項框定為重寫或重構,但這些術語,正如我們將要看到的那樣,實際上只是擺在我們面前的一系列選項的替代品。

接手了嚴重過時的軟件,到底是該逐步重構還是摧毀重寫呢? 1

通過重構,在現代化遺留應用程序的場景中,通常意味著我們將會保持應用程序基本不變,但會進行一些微小的內部改進以解決特定的問題(如可維護性、可擴展性等) 。 另一方面,通過重寫,則意味著我們打算“從頭開始”,或者換句話說,進行重大的變更。

但這只會引出下一個問題! 較小變更(Minor)和重大變更(Major)到底是什麼意思? 如果我們打算將前端框架從AngularJS升級為React,但保留後端服務不變,這是重構還是重寫呢? 或者,如果我們想要將一個單體應用拆分成三個不同的微服務,但只是複制粘貼業務邏輯到新的版本控制存儲庫中,那這是重寫、重構還是其他什麼呢? 我們真的在乎嗎? 給我們的努力貼上標籤真的很重要嗎?

是的,確實是。 雖然我們的工作是構建可運行的軟件,而不是對語義進行哲學思考,但我們使用的詞彙確實會產生影響。 當我們提議走重寫或重構的道路時,業務和技術涉眾應該能夠準確地理解我們的意思,以及需要付出什麼樣的努力。 換句話說,我們措辭的精確性將有助於我們更好地設定預期。 此外,當我們穿過一些概念上的迷霧並找到更清晰的定義時,也會讓我們對這個決定有一個更細緻的看法,並能使我們脫離狹隘的重寫或重構框架。

所以,就像任何一次長途旅行一樣,在我們跳上車出發之前,讓我們花點時間整理行李。 我們不想出現在海灘上才發現自己忘了帶泳衣。

功能改進

一個好的起點是定義重寫和重構不是什麼,它們是改進應用程序功能的策略。 這種類型的工作,無論是修復缺陷,交付新特性,還是清理用戶界面,我們都可以稱之為增強。 它是關於改進應用程序為用戶所做的事情的,正如我們稍後將看到的那樣,它是正常的開發狀態。

接手了嚴重過時的軟件,到底是該逐步重構還是摧毀重寫呢? 2

但在某些情況下,功能增強的範圍可能相當大。 例如,企業可能想要確定某應用程序是為正確的用戶群提供服務的,但所有的功能都需要徹底檢查。 這種情況也可能被稱為重寫,但是在這裡我們要做一個區分。 因為這種類型的工作需要構建所有新的功能,所以它與新建項目基本上沒有區別。 當需要定義新的功能需求時,從零開始開發一個獨立的系統,並且不能繼承原邏輯或代碼,我們會將其視為新開發的應用程序,而不是重寫。

重構是什麼

向應用程序添加功能並不是本文的重點。 我們的場景是:應用程序通常會執行預期的操作,但缺少如何執行的能力,換句話說,即缺少系統的非功能或質量屬性。 例如,用戶可能對這些功能感到滿意,但應用程序可能過於難以維護,或者可能頻繁崩潰,或者在峰值負載下性能很差。 當這些非功能屬性缺失時,我們才會考慮重寫或重構。

關於重構,我們經常使用這個術語來指代不同的工作範圍。馬丁·福勒在他的《重構》一書中是這樣定義重構:

重構是一種用於重組現有代碼主體,在不更改其外部行為的情況下更改其內部結構的規範技術。

從這種純粹的意義上講,重構主要是為了使代碼更易於維護。 這可能是分解冗長的或複雜的函數,修復不一致的命名,添加單元測試,或者重組類的層次結構、數據結構或模式。 請注意,沒有更改任何對用戶可見的內容,但是修改了內部的代碼結構,使其更容易為開發人員所使用,從而提高了我們的工作效率(和幸福感!)。

然而,在我們做重寫或重構決策的場景中,這個定義過於嚴格。 在我們的場景中,當我們談論重構時,我們通常不會區分內部和外部,而是會區分功能和非功能。 例如,我們可能會說,我們選擇重構現有的代碼庫,以提高應用程序的可靠性或性能。 從技術上講,這些質量屬性不是系統的內部屬性(用戶可以明顯感知到它們,因為它們直接影響用戶),它們只是非功能性的。 這可能是一個過於學術的區別,但本著精確的精神,我認為有必要指出來。 在本文中,我們將使用更廣泛的重構定義:

重構是一種方法,通過這種方法對現有的代碼主體進行增量重組,以提高系統的質量屬性。

最後,需要注意的是重構是關於迭代變更的。 它會對應用程序進行細微的調整,將其交付,然後沖洗並重複。 在功能增強的基礎上,重構可以使我們的用戶滿意,使我們的代碼庫保持健康,並能最大限度地減少技術債和功能缺陷。 然而,如果被忽略,我們可能需要考慮更重的替代方案。

重寫是什麼

與重構一樣,重寫也有著相同的基本目標:改善應用程序的非功能性。 區別在於更改了多少。 簡單地說,如果重構是管道膠帶,那麼重寫就是一個大錘或一個反鏟。 它不是要對現有的功能進行漸進式的改進,而是要摧毀它,重新構建。 對應於我們討論過的其他類型的開發工作,我們可以這樣可視化地展示重寫:

接手了嚴重過時的軟件,到底是該逐步重構還是摧毀重寫呢? 3

可以說,重寫是一項涉及到對系統進行重大更改的工作,以便對其質量屬性進行根本性的改進。 但也有灰色區域。 重寫工作通常會擴散到其他像限。 例如,一個應用程序可能會因為技術債而癱瘓,以至於幾乎不可能再添加新特性。 我們可能會選擇重寫然後建立一個新的基礎來提高可維護性和可擴展性(質量屬性),但是在重寫的過程中,我們也可能會加入一些新的特性來滿足業務需求。 它基本上是重寫的,但也進行了一些增強。

同樣地,在重寫和重構的邊界上也存在一些模糊性。 在一些“平移”(lift-and-shift)的情況下,系統被遷移到一個新的平台上,使得它在本質上成為了一個不同的應用程序,但其中的代碼實現基本相同,即沒有重構。 這感覺像是重寫了,但真的是這樣嗎? 需要做多少更改才能被視為是重寫呢?

再次,讓我們看看是否可以增加一些精度。 在本書中,我們使用以下定義:

重寫就是重新構建存在於遺留應用程序中的相同功能,但使用不同的語言/框架,在新的代碼庫(不僅僅是分支)中維護,並作為一個全新的構件進行部署(可能部署到不同的平台上,如服務器、硬件、無服務器、客戶端等)。

換句話說,我們要畫一些明確的界限。 例如,如果我們要重寫一個重要的函數、類甚至模塊,但是我們的工作是在代碼庫主線的分支上完成的,那麼這就不是重寫。 同樣地,如果我們重新實現了應用程序的一部分,但是系統本身仍作為同一構件(二進製文件、WAR等)部署,這也不是重寫。 在我們的場景中,重寫是很大的(BIG)。 它們是涉及需要構建和部署全新應用程序的重大更改。 是的,在實現這一目標的過程中可能會有一些漸進的步驟,我們稍後將會看到,但這是一種與重構根本不同的工作。

為了幫助自己梳理具體的情況,你可以把它畫出來。 即對於可能現代化或改進應用程序的不同途徑,究竟要更改些什麼? 這裡有一個例子:

接手了嚴重過時的軟件,到底是該逐步重構還是摧毀重寫呢? 4

實際上,變更的性質可能與重寫或重構的定義不一致,但這沒有關係。 例如,上圖可能表示了這樣一種情況:我們提議使用一組更現代化的技術來重新實現某個服務,但同時保持公開的API和底層持久層結構不變。 這是一個較小變更和重大變更的混合體,所以應該如何確切地標記它可能仍然不清楚。 然而,重要的是,我們已經了解了更深層次的細節,這將有助於我們更好地思考和證明這個決定。

現在我們的準備工作已經差不多完成了,但是在我們開始我們的旅程之前,讓我們先把它們放在一起,看看這些不同類型的開發工作是如何適應給定應用程序生命週期的。 換言之,讓我們探究一下起源故事,以便重寫。

重寫的起源故事

這一切都是從新建開發階段開始的,在這個階段我們有了一個想法,並開始從中構建一個功能強大的應用程序。 經過數週或數月的不懈努力,某些產品最終被投放到“市場”(可能是實際的付費客戶,或只是一組內部業務用戶,等等)。 如果該應用程序很受歡迎,它將會在一段時間內處於增強的平衡階段,在此階段會添加新功能並修復缺陷。 每個人都很開心。 但最終,技術債會累積起來,我們開始看到努力的回報在遞減,雖然在過去一周的開發足以添加一個全新的功能,但現在一周卻不足以改變一個按鈕的顏色。

接手了嚴重過時的軟件,到底是該逐步重構還是摧毀重寫呢? 5

在這一點上,我們可能會質疑是否值得投入更多的時間和金錢。 此外,自從應用程序首次發布以來,可能已經出現了一些令人興奮的新技術,我們可能會對如何利用這些技術來使我們的應用程序更具彈性、更易於使用、更具性能等抱有一些宏偉的設想,因此我們開始製定重寫計劃。 其想法是在短時間內凍結現有系統的開發,然後將資源轉移到替換系統上。 我們將首先構建基礎(使用更現代化的模式、工具、語言等等),然後將現有的功能遷移到該基礎中。 用戶只需要安然度過“暫停”(即不需要任何新的更新),但當重寫系統就位時,工作效率就會是之前的兩倍(或更多!)。

雖然這個計劃看起來很直接,但它掩蓋了一些關鍵的風險:技術、組織和心理因素,所有這些因素都會導致重寫階段是極不穩定的。 隨著這一階段的拖延,我們成功替換的機會會越來越渺茫。 在接下來的文章中,我們將探討一些隱藏在重寫工作中的危險,以及為什麼我們總是不顧這些危險勇往直前的原因。

原文鏈接:

http://www.bennorthrop.com/rewrite-or-refactor-book/chapter-1-what-we-mean-by-rewrite-and-refactor.php