重构还是重写?
作者 | Ben Northrop
策劃 | Tina
世上的大難題是什么?莎士比亞說:“生存還是毀滅,這是一個值得考慮的問題。”同樣,對開發者來說,重構還是重寫,也是一個值得考慮的問題。
假設,我們有一個應用程序深陷技術債,已經嚴重過時。面對這種情況,我們需要了解最佳選項——是艱難地探索并進行重構,還是摧毀它進行重寫,哪一種方式更有意義?
讓我們先解決一個“避而不談”的問題:對任何需要改進的遺留應用程序,下一步的行動并非一個簡單決定。我們會將選項定為重寫或重構,但是,它們只是擺在我們面前一系列選項的替代品。
在現代的遺留應用程序場景中,重構意味著我們會保持應用程序基本不變,但會進行一些小的內部改進,來解決特定問題,比如可維護性、可擴展性等。另一方面,重寫則意味著我們打算“從頭開始”,換句話說,進行重大的變更。
但是,這會引出下一個問題。Minor(小的變更)和 Major(重大變更)到底是什么意思?
如果我們打算將前端框架從 AngularJS 升級為 React,但后端服務不變,這是重構還是重寫?如果我們想把一個單體應用拆分成 3 個不同的微服務,但只是復制粘貼業務邏輯到新的版本控制存儲庫中,那這是重寫、重構還是其他什么呢?
雖然我們的工作是構建可運行的軟件,而非對語義進行哲學層面的思考,但詞語會對我們產生影響。當我們提議重寫或重構時,業務和技術人員應該能準確理解我們的意思,以及需要付出什么樣的努力。換句話說,措辭的精確性有助于我們更好地設定預期。
此外,當我們對概念有了更好的理解并找到更清晰地定義時,這會讓我們對這個決定有更細致的看法,并能讓我們擺脫狹隘的重寫或重構。
因此,就像一次長途旅行一樣,在我們出發前,花點時間整理行李,你肯定不想到了海灘才發現自己忘了帶泳衣。
1 功能改進
一個好的開始是定義重寫和重構不是什么,它們被視為一種改進應用程序功能的策略。這種類型的工作,無論是修復缺陷,交付新特性,還是清理用戶界面,我們都可以稱之為增強。它是改進應用程序為用戶所做的事情的,正如我們稍后看到的那樣,它是開發的正常狀態。
但在某些情況下,功能增強的范圍可能相當大。例如,企業可能想確定某款應用程序服務于正確的用戶群,但是所有功能都需要徹底檢查。這種情況也可能被稱為重寫,但是,我們要在這里做一個區分。
因為這種類型的工作需要構建所有新功能,所以,它與新項目基本沒有區別。當需要定義新的功能需求時,從零開始開發一個獨立系統,并且沒有繼承原來的邏輯或代碼,我們會將其視為新開發的應用程序,而不是重寫。
2 到底什么是重構?
向應用程序添加功能,這并非本文的重點。我們的場景是:應用程序通常會執行預期操作,但缺少如何執行的能力。換句話說,即缺少系統的非功能或質量屬性。例如,用戶可能對這些功能很滿意,但應用程序卻很難維護,或者可能頻繁崩潰,或者性能在峰值負載下很差。
當這些非功能屬性缺失時,我們才會考慮重寫或重構。
http://www.qasigma.com/2008/12/software-quality-attributes.html
關于重構,我們經常用這個術語來指代不同的工作范圍。Martin Fowler 在他的《重構》一書中這樣定義重構:
重構是一種用于重組現有代碼主體,在不更改其外部行為的情況下更改其內部結構的規范技術。
https://martinfowler.com/books/refactoring.html
從這種純粹的意義上講,重構主要是為了讓代碼更易于維護。這可能是分解冗長的或復雜的函數,修復不一致的命名,添加單元測試,或者重組類的層次結構、數據結構或模式。請注意,沒有更改任何對用戶可見的內容,但是修改了內部的代碼結構,讓其更容易為開發人員所使用,從而提高了我們的工作效率(和幸福感!)。
然而,在我們做重寫或重構決策的場景中,這個定義過于嚴格。在我們的場景中,當談論重構時,我們通常不會區分內部和外部,而是會區分功能和非功能。例如,我們可能會說,選擇重構現有的代碼庫,以提高應用程序的可靠性或性能。從技術上講,這些質量屬性不是系統的內部屬性(用戶可以明顯感知到它們,因為它們直接影響用戶),它們只是非功能性的。這可能是一個過于學術的區別,但本著精確的精神,我認為有必要指出來。
在本文中,我們將使用更廣泛的重構定義:
重構是一種方法,通過這種方法對現有的代碼主體進行增量重組,以提高系統的質量屬性。
最后,需要注意的是重構是關于迭代變更的。它會對應用程序進行細微調整,進行交付,然后沖洗并重復。在功能增強的基礎上,重構可以使我們的用戶滿意,使我們的代碼庫保持健康,并最大限度地減少技術債和功能缺陷。然而,如果被忽略,我們可能需要考慮更重的替代方案。
3 重寫是什么
與重構一樣,重寫也有著相同的基本目標:改善應用程序的非功能性。區別在于更改了多少。簡單地說,如果重構是管道膠帶,那么重寫就是一個大錘或一個反鏟。它不是要對現有的功能進行漸進式改進,而是要摧毀它,重新構建。對應于我們討論過的其他類型的開發工作,我們可以這樣可視化地展示重寫:
可以說,重寫是一項涉及對系統進行重大更改的工作,以便對其質量屬性進行根本性改進。但也有灰色區域。重寫工作通常會擴散到其他象限。例如,一個應用程序可能會因為技術債而癱瘓,以至于幾乎不可能再添加新特性。我們可能會選擇重寫,然后建立一個新的基礎來提高可維護性和可擴展性(質量屬性),但是在重寫過程中,我們也可能加入一些新特性來滿足業務需求。它基本上是重寫的,但也進行了一些增強。
同樣地,在重寫和重構的邊界上也存在一些模糊性。在一些“平移”(lift-and-shift)的情況下,系統被遷移到一個新平臺,讓它在本質上成為一個不同的應用程序,但其中的代碼實現基本相同,即沒有重構。這感覺像是重寫了,但真的是這樣嗎?需要做多少更改才能被視為是重寫?
再次,讓我們看看是否可以增加一些精度。在本書中,我們使用以下定義:
重寫就是重新構建存在于遺留應用程序中的相同功能,但使用不同的語言 / 框架,在新的代碼庫(不僅僅是分支)中維護,并作為一個全新的構件進行部署(可能部署到不同的平臺上,如服務器、硬件、無服務器、客戶端等)。
換句話說,我們要畫一些明確的界限。例如,如果我們要重寫一個重要的函數、類甚至模塊,但是我們的工作是在代碼庫主線的分支上完成的,那么這就不是重寫。同樣地,如果我們重新實現了應用程序的一部分,但是系統本身仍作為同一構件(二進制文件、WAR 等)部署,這也不是重寫。在我們的場景中,重寫是很大的(BIG),涉及需要構建和部署全新應用程序的重大更改。是的,在實現這一目標的過程中,可能會有一些漸進的步驟,我們稍后會看到,但這是一種與重構根本不同的工作。
為了幫助自己梳理具體的情況,你可以把它畫出來。即對于可能現代化或改進應用程序的不同途徑,究竟要更改些什么?這里有一個例子:
實際上,變更的性質可能與重寫或重構的定義不一致,但這沒有關系。例如,上圖可能表示了這樣一種情況:我們提議使用一組更現代化的技術來重新實現某個服務,但同時保持公開的 API 和底層持久層結構不變。這是一個較小變更和重大變更的混合體,所以應該如何確切地標記它可能仍然不清楚。然而,重要的是,我們已經了解了更深層次的細節,這將有助于我們更好地思考和證明這個決定。
現在我們的準備工作已經差不多完成了,但是在我們開始旅程前,讓我們先把它們放在一起,看看這些不同類型的開發工作是如何適應給定應用程序生命周期的。
換言之,讓我們探究一下起源故事,以便重寫。
4 重寫的起源故事
這一切都是從新的開發階段開始的。在這個階段,我們有了一個想法,并開始從中構建一個功能強大的應用程序。經過數周或數月的不懈努力,某些產品最終被投放到“市場”(可能是實際的付費客戶,或只是一組內部業務用戶,等等)。
如果該應用程序很受歡迎,它會在一段時間內處于增強的平衡階段。在此階段,我們會添加新功能并修復缺陷。每個人都很開心。
但最終,技術債會累積起來,我們開始看到努力的回報在遞減,雖然在過去一周的開發足以添加一個新功能,但現在一周卻不足以改變一個按鈕的顏色。
在這一點上,我們可能會質疑這是否值得投入更多的時間和金錢。
此外,自從應用程序首次發布以來,可能已經出現了一些令人興奮的新技術。對如何利用這些技術來讓應用程序更有彈性、更好用和性能更好等,我們抱有一些宏偉的設想。因此,我們開始制定重寫計劃。想法是在短時間內,凍結現有系統的開發,然后將資源轉移到替換系統上。我們將首先構建基礎(使用更現代化的模式、工具、語言等),然后將現有的功能遷移到該基礎中。用戶只需要安然度過“暫停”(即不需要任何新的更新),但當重寫系統就位時,工作效率就會是之前的兩倍(或更多!)。
雖然這個計劃看起來很直接,但它掩蓋了一些關鍵的風險:技術、組織和心理因素,所有這些因素都會導致重寫階段是極不穩定的。
原文鏈接:
http://www.bennorthrop.com/rewrite-or-refactor-book/chapter-1-what-we-mean-by-rewrite-and-refactor.php
總結
- 上一篇: 中年架构师的20个小时,全靠忍!
- 下一篇: 一个简单案例,带你看懂GC日志!