重構的原則

周小帥發表於2019-09-06
  1. 何為重構:

    • 很多人使用 「重構」 這個詞來指代任何形式的程式碼清理。
    • 重構的關鍵在於運用大量微小且保持軟體行為的步驟,一步步稻城大規模的修改。每個單獨的重構要麼很小,要麼由若干小步驟組合而成;因此,在重構的過程中,程式碼很少進入不可工作的狀態,即便是沒有完成重構,也可以在任何地方停下來;
    • 「可觀察行為」:整體而言,經過重構的程式碼所做的事應該與重構之前大致一樣;
  2. 兩頂帽子:

    • 把時間分配給兩種截然不同的行為:新增新功能和重構;
    • 新增新功能時,不應該修改既有程式碼,只管新增新功能,透過測試讓程式正常執行;
    • 重構時,不能再新增新功能,只管調整程式碼的結構,此時不應新增任何測試。只在絕對必要時才去修改測試;
    • 軟體開發工程中,可能會發現自己會經常換帽子,但無論何時都得清楚自己戴的事那一頂帽子,並且明白不同的帽子對變成狀態提出的不同要求;
  3. 為何重構:

    • 重構改進軟體設計:

      • 如果沒有重構,程式的內部設計會逐漸腐敗變質;
      • 程式碼結構的流失有累積效應,越難看出程式碼所代表的設計意圖,就越難保護其設計,於是設計就腐敗的越快。
      • 經常性的重構有助於程式碼維持自己該有的形態;
      • 改進設計的一個重要方向就是消除重複程式碼,程式碼量減少並不會使系統執行更快,因為這對程式的資源佔用幾乎沒有任何明顯影響,然而程式碼量減少將使未來可能的程式修改工作容易的多;
      • 消除重複程式碼,就可以確定所有事物和行為在程式碼中只表述一次,這正是優秀設計的根本;
    • 重構使軟體更容易理解:

      • 重構可以幫我讓程式碼更易讀;
    • 重構幫助找到 bug :

      • 對程式碼的理解,可以幫我找到bug;
      • 對程式碼進行重構,就可以深入理解程式碼的所作所為,並立即把新的理解反映在程式碼當中;
      • 重構可以幫助開發者更有效的寫出健壯的程式碼;
    • 重構提高程式設計速度:

      • 重構可以幫助開發者更快速的開發程式;
      • 重構可以改善設計,提升可讀性,減少bug,這些都能提高質量。花在重構上的時間確實可以提高開發效率;
      • 如果程式碼清晰,引入 bug 的可能性就會變小,即使引入了 bug ,除錯也會容易的多。理想情況下,我的程式碼庫會逐步演化成一個平臺,在其上可以很容易的構造與其領域相關的新更能;
      • 透過投入精力改善內部設計,增加了軟體的耐久性,從而可以更長時間的保持開發的效率;
      • 由於預先作出良好的設計非常困難,想要既體面又快速的開發功能,重構必不可少;
    • 何時重構:

      • 三次法則:當程式碼重複出現三次,就必須要重構了;

      • 預備性重構:

        • 重構的最佳時機就是在新增新功能之前,在動手新增之前,先要根據原有程式碼庫設計好重構方法;
        • 「出發前先規劃好最有的路線很重要」;
        • 修復 bug 時的情況也是一樣,如果把3段一摸一樣且都會導致錯誤的程式碼合併到一處,問題修復起來會容易的多。或者,用重構改善這些情況,在同樣場合再次出現同樣的bug 的機率和會降低;
      • 幫助理解的重構,使程式碼更易懂:

        • 需要首先理解程式碼再做什麼,然後才能著手修改;
        • 透過重構,就把業務的理解轉移到程式碼本身,隨後執行軟體,檢查是否正常執行,來檢查這些理解是否正確;
        • 如果把對程式碼的理解植入程式碼中,這份知識會儲存的更久,並且我的同事也能看到;
        • 當程式碼變得清晰時,就會看到之前看不見的設計問題;
        • 在研讀程式碼時,重構會引領開發者獲得更高層面的理解,如果只是閱讀程式碼很難有此領悟;
      • 撿垃圾式重構:

        • 遇到問題程式碼時,進行少量的修復不影響正常邏輯;
        • 如果每次經過這段垃圾程式碼時都把它變好一點,積少成多,垃圾總會被清理乾淨。重構的妙處在於,每個小步驟都不會破壞程式碼,所以,有時一塊垃圾程式碼在好幾個月之後才終於清理乾淨,但即使每次清理並不完整,程式碼也不會被破壞;
      • 有計劃的重構和見機行事的重構:

        • 預備性重構,幫助理解性重構,撿垃圾式重構,都是見機行事的,並不會安排一段時間來重構,而是在新增功能或者修復 bug 的同時順便重構,這是自然程式設計流的一部分;
        • 絕大多數重構都是開發者在做其他事的過程中自然發生;
        • 「每次要修改時,首先令修改很容易,然後進行這個容易的修改」
        • 優秀的程式設計師知道,新增新功能最快的方法往往是先修改現有的程式碼,是新功能容易被加入。所以,軟體永遠不應該被視為“完成”,每當需要新能力時,軟體就應該作出相應的改變,越是在已有程式碼中,這樣的改變就越顯重要;
        • 重構的思想要應用在日常開發中,不應該在專門的時間內;
        • 重構常與新新增功能緊密交織,不值得花工夫把他們分開,並且這樣做也使重構脫離了上下文,使人看不出這些“重構提交”的價值;
      • 長期重構

        • 如果不願讓一支團隊專門做重構,可以讓整個團隊達成共識,在未來幾周時間裡逐步解決這個問題,這是一個有效的策略,好處在於不會破壞程式碼,每次小改動之後,整個系統仍然照常工作;
      • 複審程式碼時重構

        • 重構可以幫助開發者複審其他同事的程式碼,開始重構前我可以先閱讀程式碼,得到一定程度的理解,並提出一些建議,一旦想到一些點子,我就會考慮是否可以透過重構立即實現它們;
        • 重構可以幫助程式碼複審工作得到更具體的結果;
        • 結隊程式設計:在程式設計的過程中持續不斷的進行程式碼複審;
      • 怎麼對經理說重構

        • 不要跟經理說程式碼重構!!!
        • 如果要修補錯誤,就得先理解軟體工作方式,而我發現重構時理解軟體的最快方式;
      • 何時不應該重構

        • 只有當我需要理解其工作原理時,對其進行重構才有價值;
        • 如果重寫比重構還容易,就別重構了;
      • 重構的挑戰

        • 瞭解重構會遇到的挑戰,這樣才能做出有效應對;

        • 延緩新功能釋出

          • 重構唯一的目的就是讓我們開發更快,用更少的工作量創造更大的價值;
          • 絕大部分人應該嘗試多做重構;
          • 我們之所以重構,因為它可以讓我們更快,新增功能更快,修復 bug 更快;
          • 重構應該總是由經濟利益驅動的;
        • 程式碼所有權

          • 修復一個函式名時,注意觀察所有的呼叫者,是否有許可權修改全部的呼叫者;
          • 程式碼所有權的邊界會妨礙重構,因為一旦自作主張的修改,就一定會破壞使用者的程式;
        • 分支

          • 「語義衝突」 :在一個分支中修改了函式名字,但是如果在其他分支中大量使用修改前的函式名,則會引入大量編譯錯誤,這個稱謂語義衝突;
          • 團隊中每個開發者都會在各自的分支上工作,做完然後合併到主分支上,「master」 稱為:特性分支;
          • 特性分支的缺點:在隔離分支上工作的越久,將完成的工作合併會主分支上就越困難;
          • 如果我修改了一個函式名,版本控制工具可以輕鬆的將我的修改與別人的程式碼整合。如果在整合之前別人新新增呼叫了這個被我改名的函式,整合之後的程式碼就會被破壞;
          • 在使用 CI 整合工具時,每個團隊成員每天至少向主線整合一次,這個實踐避免了任何分支彼此差異太大,從而極大的降低了合併的難度;
          • 重構經常需要對程式碼庫中的很多地方做很小的修改,這樣的修改尤其容易造成合並時的語義衝突;
        • 測試

          • 不會改變程式可觀察的行為,這是重構的一個重要特性;
          • 既然每個重構都是很小的改動,即便真的造成了破壞,只需要檢查最後一步的小修改。就算找不到原因也只需要回滾到最後一個可用版本就行了;
          • 自測試程式碼與整合緊密相關,我們仰賴持續整合來捕獲分支整合造成的語義衝突;
        • 遺留程式碼

          • 重構可以很好的幫助我們理解遺留系統,引人誤解的函式名可以改名,使其更好的反映程式碼用途;
          • 開發者需要隨時重構相關的程式碼;每次觸碰一塊程式碼嘗試著把它變好一點,至少要讓營地比我到達時更乾淨;
        • 資料庫

          • 資料庫是重構經常出問題的一個領域;
          • 和通常的重構一樣,資料庫的重構的關鍵也是小步修改並且每次修改都應該完整,這樣每次遷移之後系統仍然能執行。由於每次遷移設計的修改都很小,寫起來容易;將多個遷移串聯起來,就能對資料庫結構及其中儲存的資料做很大的調整;
          • 資料庫重構最好的是分散到多次生產釋出來完成,這樣即便某次修改再生產資料庫上造成問題,也比較容易回滾;
        • 重構,架構和YAGNI

          • 「YAGNI」 : “你不會需要它”(you aren?t going to need it)的縮寫。YAGNI並不是“不做架構性思考”的意思,不過確實有人以這種欠考慮的方式做事。我把YAGNI視為將架構、設計與開發過程融合的一種工作方式,這種工作方式必須有重構作為基礎才可靠。
          • 重構極大的改變了人們考慮軟體架構的方式;
          • 修改遺留程式碼經常很油挑戰,尤其是遺留程式碼缺乏恰當的測試時;
          • 重構對架構最大的影響在於,透過重構,我們能得到一個設計良好的程式碼庫,使其能夠優雅的應對不斷變化的需求;
          • 如果一種靈活性機制不會增加複雜度「比如新增幾個命名良好的小函式」可以引入它;但如果一種靈活性會增加軟體複雜度,就必須證明自己值得被引入。如果不同的呼叫者不會傳入不同的引數值,那麼就不會新增這個引數;
        • 重構與軟體開發過程

          • 重構是否有效,與團隊採用的其他軟體開發實踐緊密相關;
          • 重構起初是作為極限程式設計的一部分被人們採用的,極限程式設計本身就融合了一組不太常見而又彼此關聯的時間,例如持續整合,自測試程式碼以及重構;
          • 重構的第一塊基石是自測試程式碼;
          • 自測試程式碼也是持續整合的關鍵環節,所有這三大實踐 ---- 自測試程式碼,持續整合,重構,彼此之間有著很強的協同效應;
          • 有這三大核心實踐打下的基礎,才談得上運用敏捷思想的其他部分。
          • 持續交付確保軟體始終處於可釋出的狀態,很多網際網路團隊能做到一天多次釋出,靠的就是持續交付的威力;
          • 持續整合也能幫我們降低風險,並使我們做到根據業務需要隨時安排釋出,而不受技術的侷限;
        • 重構與效能

          • 為了讓阮家易於理解,我常會做出一些使程式執行變慢的修改;
          • 雖然重構可能使軟體執行更慢,但它也使軟體的效能最佳化更容易,除了對效能有嚴格要求的實時系統,其他任何情況下「編寫快速軟體」的秘訣就是:先寫出可調優的軟體,然後調優它以求獲得足夠的速度;
          • 關於效能:對大多數程式進行分析就會發現大半時間都耗費在一小半程式碼上,如果一視同仁的最佳化所有程式碼,90%的程式碼最佳化都是白費的,因為被最佳化的程式碼很少執行;
          • 如果因為缺乏對程式的清楚認識而花費時間,那些時間就都被浪費掉了;
          • 一個良好的程式可以從兩方面幫助這一最佳化:首先,它讓我有比較充裕的時間進行效能調整,因為有構造良好的程式碼在手,我能夠更快速的新增功能,也就有更多的時間再效能問題上。「準切的度量則保證我把這些時間投在恰當地點」;
          • 其次面對構造良好的程式,在進行效能分析時便便有較細的粒度。度量工具會把我帶入範圍較小的程式碼中,而效能的調整也比較容易些。由於程式碼更加清晰,因此可以更好的理解自己的選擇,更清楚那種調整起關鍵作用。
          • 重構可以幫助開發者寫出更快的軟體,短期來看,重構的確可能使軟體變慢,但它使最佳化階段的軟體效能調優更容易,最終還是會得到好的效果;
本作品採用《CC 協議》,轉載必須註明作者和本文連結
周小帥

相關文章