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

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

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

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

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

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

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

功能改進

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

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

重構是什麼

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

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

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

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

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

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

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

重寫是什麼

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

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

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

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

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

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

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

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

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

重寫的起源故事

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

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

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

原文鏈接:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章