優秀的開發人員知道何時以及如何重構 Rails 代碼
已發表: 2022-03-11大規模重構:你為什麼要做這樣的事情?
如果它沒有壞,就不要修理它。
這是一個眾所周知的短語,但正如我們所知,大多數人類技術進步都是由那些決定修復未損壞的東西的人取得的。 尤其是在軟件行業,人們可能會爭辯說,我們所做的大部分工作都是修復未損壞的部分。
修復功能、改進 UI、提高速度和內存效率、添加功能:這些都是很容易看出它們是否值得做的活動,然後作為經驗豐富的 Rails 開發人員,我們會爭論或反對將時間花在這些活動上。 但是,有一個活動,大部分都屬於灰色地帶——標準重構,尤其是大規模代碼重構。
大規模重構這個術語值得解釋。 究竟什麼可以被認為是“大規模”會因具體情況而異,因為這個術語有點模糊,但我認為任何顯著影響的不僅僅是幾個類,或者不僅僅是一個子系統,而且它的接口是“大。” 另一方面,任何隱藏在單個類接口後面的 Rails 重構肯定是“小”的。 當然,兩者之間有很多灰色地帶。 最後,相信你的直覺,如果你害怕這樣做,那麼它可能是“大”的。
根據定義,重構不會產生任何可見的功能,您無法向客戶展示任何內容,也沒有可交付成果。 充其量它們可能會產生小的速度和內存使用改進,但這不是主要目標。 有人可能會說,主要目標是您滿意的代碼。 但是因為你正在以這樣一種方式重新排列代碼,它會在整個代碼庫中產生深遠的影響,所以有可能所有的地獄都會崩潰並且會出現問題。 這當然是我們提到的恐懼的來源。 你有沒有向你的代碼庫介紹過一個新人,在他們詢問了一段特別有組織的代碼之後,你的回答是這樣的:
Yeeeaahh,這是當時有意義的遺留代碼,但規範發生了變化,現在修復它太貴了?
也許你甚至給了他們一個非常嚴肅的表情,並告訴他們不要動它,不要碰它。
問題是,“我們為什麼要這樣做?” 這是一個自然的過程,並且可能與重構過程一樣重要,因為您經常必須說服其他人允許您將昂貴的時間花在重構上。 因此,讓我們考慮一下您想要這樣做的情況以及獲得的好處:
性能改進
從可維護性的角度來看,您對代碼的當前組織感到滿意,但它仍然會導致性能問題。 優化當前設置的方式太難了,而且更改將非常脆弱。
這裡只有一件事要做,那就是廣泛地分析它。 運行基準並估計您將獲得多少,然後嘗試估計它將如何轉化為具體收益。 有時您甚至可能意識到建議的代碼重構並不值得。 其他時候,你會有冷硬的數據來支持你的案子。
架構改進
也許架構還可以,但有些過時,或者每次接觸代碼庫的那部分時,它都太糟糕了,你會畏縮。 它運行良好且快速,但添加新功能很痛苦。 重構的商業價值就在於這種痛苦。 “痛苦”還意味著重構過程將花費更長的時間來添加新功能,甚至可能更長。
並且可以獲得好處。 估算一些示例功能的成本/收益,無論是否有您提議的大重構。 說明在系統開發過程中,類似的差異將適用於現在和將來永遠觸及系統該部分的大多數即將推出的功能。 您的估計可能是錯誤的,因為它們經常出現在軟件開發中,但它們的比率可能會在大致範圍內。
使其保持最新狀態
有時代碼最初編寫得很好。 你對它非常滿意。 它速度快、內存效率高、可維護且與規範完全一致。 原來。 但是隨後規格發生變化,業務目標發生變化,或者您從最終用戶那裡了解到一些新的東西,這會使您最初的假設無效。 代碼仍然運行良好,您仍然對此感到非常高興,但是當您在最終產品的上下文中查看它時,有些事情會很尷尬。 事物被放置在稍有錯誤的子系統中,或者屬性位於錯誤的類中,或者某些名稱可能不再有意義。 他們現在正在履行一個在商業術語中完全不同的角色。 然而,仍然很難證明任何類型的大規模 Rails 重構是合理的,因為所涉及的工作將與任何其他示例一樣規模化,但好處卻不那麼明顯。 當您考慮它時,維護它甚至不是那麼難。 你只需要記住,有些東西實際上是另外一回事。 您只需要記住,A 實際上意味著 B,而 A 上的屬性 Y 實際上與 C 相關。
這就是真正的好處。 在神經心理學領域,有許多實驗表明我們的短期或工作記憶只能容納 7+/-2 個元素,其中之一是 Sternberg 實驗。 當我們研究一門學科時,我們從基本元素開始,最初,當我們考慮更高層次的概念時,我們必須考慮它們的定義。 例如,考慮一個簡單的術語“鹽漬 SHA256 密碼”。 最初,我們必須保留“salted”和“SHA256”的工作記憶定義,甚至可能保留“哈希函數”的定義。 但是一旦我們完全理解了這個術語,它只佔用一個內存槽,因為我們直觀地理解它。 這就是為什麼我們需要充分理解較低層次的概念才能對較高層次的概念進行推理的原因之一。 對於我們項目的特定術語和定義也是如此。 但是,如果我們每次討論代碼時都必須記住翻譯到真正含義的翻譯,那麼該翻譯就會佔用另一個寶貴的工作記憶槽。 它會產生認知負擔,並使通過代碼中的邏輯進行推理變得更加困難。 反過來,如果更難推理,則意味著我們更有可能忽略重要的一點並引入錯誤。
並且不要忘記更明顯的副作用。 在與我們的客戶或任何熟悉正確業務術語的人討論更改時,很有可能會產生混淆。 加入團隊的新人必須熟悉業務術語以及代碼中的對應術語。
我認為這些原因非常有說服力,並且在許多情況下證明了重構的成本是合理的。 不過,請注意,可能有很多邊緣情況,您必須使用最佳判斷來確定何時以及如何進行重構。
歸根結底,大規模重構是好的,原因與我們許多人喜歡開始一個新項目的原因相同。 你看著那個空白的源文件,一個勇敢的新世界開始在你的腦海中盤旋。 這一次你做對了,代碼會很優雅,它的佈局會很漂亮,而且快速、健壯且易於擴展,最重要的是,每天工作都會很愉快。 重構,無論規模大小,都可以讓您重新獲得那種感覺,為舊代碼庫注入新的活力並償還技術債務。
最後,最好是由計劃驅動重構,以便更容易實現某個新功能。 在這種情況下,重構將更加集中,並且重構所花費的大量時間也將通過更快地實現特性本身而立即得到回報。
準備
確保您的測試覆蓋率在您可能接觸的所有代碼庫區域中都非常好。 如果您發現某些部分沒有很好地覆蓋,請先花一些時間提高測試覆蓋率。 如果您根本沒有測試,那麼您應該首先花時間創建這些測試。 如果您無法創建合適的測試套件,請專注於驗收測試並儘可能多地編寫,並確保在重構時編寫單元測試。 從理論上講,您可以在沒有良好測試覆蓋率的情況下進行代碼重構,但這需要您進行大量手動測試並且經常這樣做。 這將花費更長的時間並且更容易出錯。 最終,如果您的測試覆蓋率不夠好,那麼執行大規模 Rails 重構的成本可能會很高,遺憾的是,您應該考慮根本不這樣做。 在我看來,這是自動化測試的一個好處,但沒有得到足夠的重視。 自動化測試允許你經常進行重構,更重要的是,更大膽地進行重構。
一旦你確定你的測試覆蓋率是好的,就該開始繪製你的更改了。 起初你不應該做任何編碼。 您需要粗略地繪製出所有涉及的更改並通過代碼庫跟踪所有後果,並將所有這些知識加載到您的腦海中。 您的目標是準確了解為什麼要更改某些內容以及它在代碼庫中扮演的角色。 如果你只是因為它們看起來需要改變或者因為某些東西壞了而這似乎可以修復它而偶然改變它,那麼你可能最終會走入死胡同。 新代碼似乎可以工作,但不正確,現在您甚至無法記住您所做的所有更改。 在這一點上,您可能需要放棄在大規模代碼重構方面所做的工作,實際上您已經浪費了時間。 因此,請花點時間探索代碼,以了解您將要進行的每項更改的後果。 它最終會得到豐厚的回報。

您將需要重構過程的幫助。 你可能更喜歡別的東西,但我喜歡一張簡單的白紙和一支筆。 我首先在論文的左上角寫下我想要進行的初始更改。 然後我開始尋找所有受變更影響的地方,並將它們寫在最初的變更下。 在這裡使用您的判斷很重要。 最終,紙上的筆記和圖表是為你自己準備的,所以選擇最適合你記憶的風格。 我寫出簡短的代碼片段,它們下方帶有項目符號,許多箭頭指向其他此類註釋,指示直接依賴它的事物(實箭頭)或間接依賴它(虛線箭頭)。 我還用速記標記註釋箭頭,以提醒我在代碼庫中註意到的一些特定事物。 請記住,在接下來的幾天內,您只會在執行計劃中的更改時返回這些筆記,使用非常簡短和隱秘的提醒是完全可以的,這樣它們使用的空間更少,並且更容易在紙上佈局. 有幾次我在 Rails 重構後的幾個月裡清理我的辦公桌,我發現了其中一篇論文。 這完全是胡言亂語,我完全不知道那張紙上的任何東西是什麼意思,除了它可能是由一個發瘋的人寫出來的。 但我知道在我處理這個問題時,那張紙是必不可少的。 此外,不要認為您需要寫出每一個更改。 您可以將它們分組並以不同的方式跟踪詳細信息。 例如,在您的主要論文上,您可以注意到您需要“將所有出現的 Ab 重命名為 Cd”,然後您可以通過幾種不同的方式跟踪細節。 您可以將它們全部寫在一張單獨的紙上,您可以計劃再次對所有出現的它執行全局搜索,或者您可以簡單地將所有需要更改的源文件留在您選擇的編輯器中並在你完成更改規劃後記下回顧它們。
當您繪製出初始更改的後果時,由於它的性質是大規模的,您很可能會確定其他更改本身會產生進一步的後果。 對它們也重複分析,注意所有相關的變化。 根據更改的大小,您可以將它們寫在同一張紙上或選擇一張新的空白紙。 在規劃更改時要嘗試和做的一件非常重要的事情是嘗試確定可以實際停止分支更改的邊界。 您希望將重構限制為最小的合理、四捨五入的更改集。 如果您看到某個點可以停止並保持原樣,即使您認為它應該被重構,即使它在概念上與您的其他更改相關,也要這樣做。 完成這一輪代碼重構,徹底測試,部署並返回更多。 您應該積極尋找這些點,以使更改的大小保持可控。 當然,一如既往地做出判斷。 很多時候,我想通過添加一些代理類來進行一些接口轉換來切斷重構過程。 我什至開始實現它們,因為我意識到它們的工作量與將重構推得更遠一點到“自然停止”(即幾乎不需要代理代碼)點一樣多。 然後我回溯,恢復我最後的更改並重構。 如果這聽起來有點像繪製未知領域的地圖,那是因為我覺得它是,除了領域地圖只是二維的。
執行
完成重構準備後,就該執行計劃了。 確保您的注意力集中並確保沒有分心的環境。 在這一點上,我有時甚至會完全關閉互聯網連接。 問題是,如果你準備充分,在你旁邊的紙上做一套很好的筆記,你的注意力就會集中起來! 您通常可以通過這種方式快速完成更改。 理論上,大部分工作都是在準備期間預先完成的。
一旦你真正在重構代碼,請注意那些做一些非常具體的、可能看起來像壞代碼的奇怪代碼。 也許它們是糟糕的代碼,但實際上它們通常是在處理一個奇怪的極端案例,該案例是在調查生產中的錯誤時發現的。 隨著時間的推移,大多數 Rails 代碼會長出“毛髮”或“疣”,它們會處理奇怪的極端情況錯誤,例如,這裡可能需要 IE6 的奇怪響應代碼或處理奇怪計時錯誤的條件。 它們對於大局並不重要,但仍然是重要的細節。 理想情況下,如果不嘗試首先覆蓋它們,它們會被單元測試明確覆蓋。 我曾經負責將一個中型應用程序從 Rails 2 移植到 Rails 3。我對代碼非常熟悉,但它有點混亂,需要考慮很多更改,所以我選擇重新實現。 實際上,這並不是真正的重新實現,因為這絕不是明智之舉,但我從一個空白的 Rails 3 應用程序開始,我將舊應用程序的垂直切片重構為新應用程序,大致使用所描述的過程。 每次我完成一個垂直切片時,我都會檢查舊的 Rails 代碼,查看每一行並仔細檢查它是否在新代碼中有對應的部分。 我基本上是在挑選所有舊代碼“頭髮”並將它們複製到新代碼庫中。 最後,新的代碼庫解決了所有的極端情況。
確保足夠頻繁地執行手動測試。 它既會迫使你在重構過程中尋找自然的“中斷”,這將允許你測試系統的一部分,也會讓你確信你沒有破壞任何你沒想到會在過程中破壞的東西.
包起來
完成 Rails 代碼重構後,請務必最後一次檢查所有更改。 查看整個差異並檢查它。 很多時候,你會注意到你在重構開始時遺漏的一些細微的東西,因為你沒有現在所擁有的知識。 這是大規模重構的一個很好的好處:您可以更清楚地了解代碼組織,尤其是如果您最初沒有編寫它。
如果可能的話,也請一位同事對其進行審查。 他甚至不必特別熟悉代碼庫的確切部分,但他應該對項目及其代碼有大致的了解。 以全新的眼光看待這些變化會大有幫助。 如果您絕對無法讓其他開發人員查看它們,那麼您將不得不假裝自己是其中的一個。 睡個好覺,並以全新的心態回顧它。
如果你缺乏質量保證,你也必須戴上那頂帽子。 再次,休息一下,遠離代碼,然後回來執行手動測試。 您剛剛經歷了相當於帶著一堆工具進入雜亂的電線櫃並將其全部整理出來,可能切割和重新佈線的東西,因此需要比平時更加小心。
最後,享受您的勞動成果,考慮到所有計劃中的更改,這些更改現在將更加清潔和易於實施。
你什麼時候不做?
雖然定期執行大規模重構以保持項目代碼的新鮮和高質量有很多好處,但它仍然是一項非常昂貴的操作。 也有不建議這樣做的情況:
您的測試覆蓋率很差
如前所述:非常差的測試覆蓋率可能是一個大問題。 使用您自己的判斷,但在短期內專注於提高覆蓋率,同時開發新功能並執行盡可能多的本地化小規模重構可能會更好。 一旦您決定冒險並對代碼庫的較大部分進行排序,這將對您有很大幫助。
重構不是由新特性驅動的,代碼庫很長時間沒有改變
我使用過去時而不是故意說“代碼庫不會改變”。 從經驗來看(我的意思是多次出錯),你幾乎永遠不能依賴你的預測來判斷代碼庫的某個部分何時需要更改。 所以,做下一件最好的事情:回顧過去,假設過去會重演。 如果某些東西很長時間沒有更改,那麼您現在可能不需要更改它。 等待這種變化出現並進行其他工作。
你時間緊迫
維護是項目生命週期中最昂貴的部分,重構使其成本更低。 任何企業都絕對有必要使用重構來減少技術債務,從而使未來的維護成本更低。 否則就有進入惡性循環的危險,在這個循環中添加新功能變得越來越昂貴。 我希望這是不言而喻的。
也就是說,大規模重構在需要多長時間方面是非常非常不可預測的,你不應該半途而廢。 如果出於任何內部或外部原因,您的時間緊迫,並且您不確定能否在該時間範圍內完成,那麼您可能需要放棄重構。 壓力和壓力,尤其是時間誘導類型,會導致較低的集中度,這對於大規模重構是絕對必要的。 努力從你的團隊那裡獲得更多的“支持”,為它留出時間,並在你有時間的時候查看你的日曆。 它沒有必要是一個連續的時間段。 當然,您還有其他問題需要解決,但這些休息時間不應超過一兩天。 如果是這樣,您將不得不提醒自己您自己的計劃,因為您將開始忘記您對代碼庫的了解以及您停止的確切位置。
結論
我希望我給了你一些有用的指導,並讓你相信在某些情況下進行大規模重構的好處,我敢說是必要的。 這個話題非常模糊,當然這裡所說的一切都不是確定的事實,具體情況會因項目而異。 我試圖提供建議,在我看來,這是普遍適用的,但與往常一樣,請考慮您的具體情況並利用您自己的經驗來適應其具體挑戰。 祝重構順利!