摘要:讓我們再回到重構的基本概念,思考我們需要怎樣的重構輔助服務。
一、背景
程式碼重構是每一位開發者最熟悉不過的字眼,其出現通常伴隨著開發過程。在程式開發、迭代與演進的漫漫長路中,某次不經意的修改就可能破壞程式原有的設計與結構,造成程式碼結構的流失,而這種流失是具有累積性的,若未及時發現與重構,程式就會逐漸腐爛甚至變質,形成巨大的歷史債務。其實重構就好比收拾房間,如果我們天天打掃,那麼每天花3分鐘就能打掃乾淨,可如果一個月不打掃,你想想需要多久才能打掃完。
既然程式碼重構在開發過程中這麼重要,怎麼能沒有相應的服務來支撐它呢?我們能不能開發出相應的“掃帚”輔助我們每天打掃房間?亦或是“掃地機器人”自動的幫我們打掃一個月未收拾的房間?
帶著上述疑問,讓我們再回到重構的基本概念,思考我們需要怎樣的重構輔助服務。
1. 什麼是重構
如此書中所說,所謂重構(Refactoring)是這樣一個過程:在不改變程式碼外在行為的前提下,對程式碼做出修改,以改程式序的內部結構。這裡的重構有兩層含義,一個名詞含義,一個動詞含義:
重構(名詞):對軟體內部結構的一種調整, 目的是在不改變軟體可觀察行為的前提下,提高其可理解性, 降低其修改成本。
重構(動詞):使用一系列重構手法, 在不改變軟體可觀察行為的前提下,調整其結構。
至此,我們應該明白,一款好的重構輔助服務應該至少兼具不變與變兩個特徵:不改變軟體可觀測行為;優化程式碼結構,降低修改成本,提高可理解性。
2. 什麼時候重構(何時應做怎樣的重構)
就重構時機問題,業界也有比較激烈的討論,有人認為重構應該隨時隨地地進行,不應該為了重構而重構,就比如我在新增新功能時、修補錯誤時、或者複審程式碼時都可以進行重構,我們暫且稱之為“開發時重構”,也有人認為“新增新功能”和“重構”是兩頂帽子,在新增新功能時,就不應該修改既有程式碼,只管新增新功能,而在重構時,就不能新增功能,只管改程式序結構,一次只做一件事,我們暫且稱之為“維護式重構”。我個人認為這兩種說法並不矛盾,真正好的重構應該是兩者的有機結合。
如上圖所示,紅色範圍是我認為比較好的重構實踐。
我認為無論在做新功能開發時還是在做老版本維護時,都適合做程式碼重構,只是適合的重構粒度不同而已。對於開發時重構,比較適合做個人級小範圍的微重構,這種重構往往影響範圍小,且較為簡單,不會對開發增加工作難度,例如“重新命名”、“函式提取”等原子重構;對於維護時重構,比較適合做架構級大範圍的複雜重構,這種重構往往是為了解決專案程式碼中遺留的技術債務,且通常和程式碼壞味道的消除結合在一起,例如依戀情結(Feature Envy)、資料泥團(Data Clumps)等,而這些壞味道的重構往往由一系列的重構原子操作組合而成。當然,最好將重構工作儘可能多地做在開發階段,儘量減少新增程式碼對已有設計與架構的破壞。
看到這裡,我們是否有些似曾相識,這不就對應了上文中提到的“掃帚”以及“掃地機器人”在實際重構工作中的應用?
3. 為什麼需要程式碼智慧重構服務
使用過現代IDE開發程式碼的同學們應該都知道,以IntelliJ IDEA為代表的很多IDE多少都自帶一些重構功能,但目前為止,這些重構都是例如“重新命名”、“函式提取”等原子性重構,只對重構過程提供了部分支援,絕大部分的程式碼、架構壞味道重構工作仍然得靠手工完成,就好比你需要打掃一個月未打掃的房子,但手中只有一把掃帚一樣。Kent Beck 說過:“手工重構仍然是很耗時的工作。正是這個簡單的事實造成了很多程式願不願意進行重構,儘管他們知道自己應該重構,但畢竟重構的成本太大了。如果能夠把重構變的像調整程式碼格式那麼簡單,程式設計師自然也會樂意像整理程式碼格式那樣整理系統的設計。而這樣的整理對程式碼的可讀性、可複用性和可理解性,都能帶來深遠的正面影響。”正因如此,一款智慧的、可以幫助開發者發現程式碼、架構中的壞味道並且引導開發者完成程式碼重構的服務尤為重要。
二、Devops全流程下的重構服務需求
對於持續交付過程中的程式碼開發與釋出環節,如上圖虛線框所示,幾乎每一步都與程式碼質量的看護或程式碼重構相關。
- 開發環節可大致分為兩種型別:用於新增新功能的“增量式開發”以及維護老版本的“存量式維護”。對於“增量式開發”而言,需要重構服務的編碼規範重構和原子重構能力在開發過程中幫助開發人員提升程式碼質量與開發效率;對於“存量式整改”而言,則需要重構服務具備架構、程式碼壞味道的檢查、重構機會點挖掘以及重構方案的推薦能力,並將相應的重構方案拆解為彼此獨立且正交的原子重構能力,最終作為一個原子重構序列推薦給開發人員,在與人機互動中一步步完成重構應用。
- 在持續釋出環節,MR門禁中的程式碼靜態檢測需要觸發對壞味道程式碼的識別,作為一種增量式檢測,主要識別由新增程式碼引入的程式碼及架構壞味道, 並自動生成一次重構任務,引導開發人員進行一次重構,避免程式碼中技術債堆積。
- 在生產運維環節,需要做定期的架構看護,將相應度量結果通過架構視覺化圖形介面展示出來,當架構某個模組腐化到一個閾值時自動報警,提示版本負責人做相應重構。
三、如何輔助開發人員實現程式碼分層重構
1. 都有哪些層級的重構
個人認為重構大致可以分為4個層級,這些層級之間的關係可以從上圖看出,並非是自底向上依次包含的關係,它們之間會有些重疊也有些不同。單純從重構本身來講,越上層級的重構操作非確定性越強,更偏業務性,而越下層級的重構操作確定性越強,更偏技術性。從智慧重構服務的角度來講,越上層越偏檢測能力,越下層越偏純重構能力。下面我們由下至上依次來看看不同層級重構操作的特點與異同。
原子重構能力:上文中也有提到原子重構,原子重構到底有哪些呢?顧名思義,即不可再分的重構操作,例如重新命名、移動、刪除等重構操作,這些重構操作是完全確定性的,例如我想要抽取一段程式碼形成一個新方法,是否可抽,可以抽成什麼樣都是完全確定的。這些重構操作因為其不可再分性位於重構最底層。
編碼規範重構:有些同學可能會對這個層級的重構比較陌生,其實它就是基於規範和規則的重構,通常包含了我們所說的微重構,例如對違反了命名風格的識別符號進行重新命名重構,亦或是對無用程式碼進行刪除的安全刪除重構等等,並且這層的重構操作完全可以通過呼叫下層的1個原子重構來實現。正因為這些重構操作基於規則,其重構結果也是相對確定的。
程式碼壞味道重構:程式碼壞味道也包括了架構壞味道,例如Feature Envy、God Class等型別,這些層級重構的目的是為了消除相應的程式碼壞味道,所以相對來說更復雜,一次重構的完成往往要呼叫下層多個原子重構操作,例如對God Class進行重構,需要呼叫至少一次Extract Class原子重構。同時這層重構的非確定性也更高,對一種程式碼壞味道的重構消除往往可以通過多種手段。
設計模式壞味道重構:這層重構應該屬於金字塔頂端的重構,因為它涉及範圍太廣,更偏向於對業務的理解與預測,從具象到抽象,顯得有些虛無縹緲。設計模式有7中壞味道和11中原則,目前無論學術界還是工業界對於這類問題檢測、重構相關的研究都還不多。
2. 智慧重構服務在不同層級下的應用
結合Devops下的重構服務需求與上圖中的四層重構,我們基本可以看出智慧重構服務不同層級的重構能力主要運用在開發工作流中的哪個階段。對於原子重構來說,因為其確定性以及業務無關性,最適合作為外掛整合在IDE上,由開發者做增量開發時呼叫;對於編碼規範重構來說,適合同原子重構一起整合進IDE外掛,在開發過程中自動且快速的識別到違反特定規則的程式碼,並輔助開發者重構;對於程式碼壞味道重構來說,適合在持續釋出環節的門禁階段攔截問題並引導開發者回到IDE進行程式碼智慧重構;對於設計模式壞味道重構來說,因為其涉及業務理解與預測,更適合放進生產運維環節,結合版本演進與迭代歷史,給出架構腐化的預測以及相應重構方案的推薦。
3. 搭積木式的重構應用
既然上層重構操作都可以轉化為最底層的原子重構操作,那如何表達這種轉化呢?表格中列舉了幾種常見的重構原子操作,每種重構原子操作都可以用一個函式來表達。例如Move Method,函式名為Move Method,我們用三個引數來明確它的行為:Source(所屬型別)、Target(目標型別)以及(需要移動的方法),有了這些資訊就可以明確一次Move Method原子重構。對於任何一個複雜的重構來說,都可以表示成如下形式的原子重構序列,即一系列原子重構的組合。
就像搭積木一樣,任何上層重構都可以通過搭積木的方式組合底層原子重構來實現。
四、重構服務設計原則
重構服務的設計亦如其它很多開發服務一樣,最終目標都是提升使用者開發效率與程式碼質量,如果二者皆得不到保證,那這款服務將被永遠丟進垃圾堆裡。從我們在華為內部的實踐經驗,總結了以下幾點原則:
充分的使用者互動:經典的重構工作流程為“小步前進,隨時可用,隨時可停,隨時回退”,小步修改意味著每一步出錯的可能性大大減小,要遵循這個流程,就離不開工具和使用者頻繁的互動,要引導使用者一步步的完成複雜的程式碼重構,每個過程都要做到隨時可用,隨時可停,隨時可退。
友好的重構工作介面:程式碼重構有點類似程式碼自動修復,但比程式碼自動修復涉及範圍更廣,如何讓使用者更好的表達重構意圖、並且一目瞭然的看到重構對原始碼架構的影響非常重要,這一部分有很多可以創新的地方。
個性化使用者配置:上層級的重構往往可以有不同的執行路徑,根據程式碼工程不同、業務場景不同,同一種壞味道重構的實現方式也可能不同,要以使用者為中心,根據業務不同、使用者不同給出個性化、定製化的重構解決方案,這一部分剛好是AI擅長的領域。
Devops服務深度整合:任何一款開發服務如果離開Devops服務就會成為一個孤立的散點,無法在開發過程中被順暢的使用,如果我們能把重構服務整合進IDE、程式碼檢視環節、程式碼入庫環節以及驗證釋出環節,就可以讓重構工具Build In在可信開發過程中,讓重構服務觸手可及。
高效:特別是在IDE上的重構分析服務,如果分析過程需要花費太長時間,程式設計師很可能就不會使用這些重構服務,他們寧可手工重構。