原文發於公衆號「百川海的小記」,一個菜鳥的自留地,歡迎關注討論spring
寫在前面:編程
1.代碼的改寫從大範圍到小範圍大體能夠分爲四級:系統級別,功能級別,代碼級別,機器級別;springboot
2.代碼級別如下改動可視爲「重構」,功能級別以上級別只能視爲「重寫」函數
3.重構是持續的平常過程,而重寫不是性能
###############正題##################測試
對於一個開發人員來講,或多或少會遇到下面的這樣的場景:優化
1.大佬:XX調度模塊跑得太慢了,胖海你這兩天把它重構一下,讓它跑得快一點線程
胖海:是是是,大佬說得對設計
2.賈苟施:XX系統的底層太臃腫了,部署也太麻煩了,咱們用springboot重構一個新系統吧日誌
胖海:是是是,您說得對
3.胖海:#@#*&@!#$,這代碼誰寫的,存心讓人看不懂啊
勞元宮:這是小坑寫的,他幾個月前已經離職了,不如你把它重構一下吧
胖海:是是是,您說得太對了
在這些場景裏面,咱們都提到「重構」,可是顯然,它們的意思並不一致。在咱們平常的語境中,很多人把「重構」看做對於一切代碼改寫的一種稱呼。那究竟「重構」是一種什麼動做呢?
重構與重寫
在經典書籍《重構-改善既有代碼的設計》(下稱《重構》)中,Martin Fowler有如下的描述:
『所謂重構(refactoring)是這麼一個過程:在不改變代碼外在行爲的前提下,對代碼作出修改,以改進程序的內部結構。重構是一種經千錘百煉造成的有條不紊的程序整理方法,能夠最大限度地減小整理過程當中引入錯誤的概率。本質上說,重構就是在代碼寫好以後改進它的設計 。』
—— Martin Fowler
簡化一下這個描述,Martin對「重構」的理解是:
重構是一個過程;
重構不改變外部行爲;
重構改進內部結構;
重構的對象是代碼;
重構有一套穩定的方法。
《重構》對於這個問題的,給出的回答是十分嚴格的。Martin在書中對重構對象的範圍限定,基本能夠理解爲單線程的,代碼庫徹底獨立的軟件項目。直到今天,這個依然是對「重構」的一個很是精準而具體的範圍定義,並且在業界的話語語境中,能夠說是一個標準的說法。可是,這放在今天的軟件系統項目中,這樣的定義卻顯得很是的苛刻了。事實上,咱們在工做中掛在口邊的重構,和這個定義自己就有很大的差異。那麼,問題出在哪裏呢?
若是分析咱們的軟件,任意一個軟件老是包含若干特性的,即若干區別於其餘軟件的表現特徵,好比軟件的可移植性,軟件功能,軟件性能等。小至Hello World,大至各種互聯網商用的綜合系統,一切軟件都有其特性。而「重構」的行爲自己,必然是對任何特性無損的。任何對於軟件特性有所改動的操做,嚴格來講都不算「重構」,我願意將這些操做稱爲「重寫」。重構與重寫的界限,在因而否可能對軟件特性形成變更。
改動範圍與級別
在一個軟件項目中,對於代碼的改寫,有大範圍的批量改寫,也有小範圍的局部改寫。我列舉如下幾個用例:
用例一:將一個以PHP爲語言開發的系統,功能原封不動地用Java從新實現
用例二:爲了準備即將發生的業務拓展,對一個支付接口進行改造,將其從固定面向單一支付渠道「X付」,改成可配置、可插拔地面向多個支付渠道,但當前依然只接入「X付」
用例三:將某個連續使用上百個if else語句的方法,改成使用衛語句進行實現,以增長其可讀性(這一改動範圍,與《重構》中的描述的重構方法影響範圍基本一致)
用例四:編寫一個方法,使得CPU運行使用率-時間曲線在排除其餘進程的影響的理想狀況下,呈正弦函數曲線(本問題重述自《編程之美》)
在以上四個用例中,從功能上來看,咱們都不難劃分出改動的影響範圍和功能使用者/調用方。我根據四個用例改動範圍的大小,拆分出四個等級。
用例 | 級別 | 調用/使用方 | 說明 |
一 | 系統級別 | 系統使用者/業務方 | 直接面向使用的業務人員 |
二 | 功能級別 | 功能接口調用方 | 面向調用接口的模塊(第二/第三方) |
三 | 代碼級別 | 調用該方法的上級方法 | 面向內部調用方法 |
四 | 機器級別 | 硬件運行指令 | 面向硬件/指令集 |
若是單純從功能上來看,咱們均可以將改動的部分視做黑盒,可是從其餘方面來看,隨着黑盒的變大,黑盒自己便愈加容易鬆散。改動範圍越小,改動用例越靠近底層,外部的影響則越小,反之亦然。
若是改動是經過機器語言直接實現的,除非將硬件的結構或者內置指令集進行修改,外部沒法動搖該軟件的任意特性;
若是僅經過高級語言進行一段代碼語句結構的調整,而不改變運行邏輯,其特性也至關穩定;
若是再向上一個級別,以功能爲改動維度,咱們發現功能模塊的實現已經不能可靠地剔除其餘代碼的影響,好比第二方的模塊引用,第三方外庫,直接或間接引用的其餘計算資源……畢竟對於一個功能的實現,能夠引用的資源是不受限制的。
到此爲止,咱們明顯看到穩定特性的黑盒已經被戳的盡是洞洞,沒法維持內部與外部的清晰界限了。如此看來,《重構》將重構的方法的範圍限制在代碼級別,確實是通過深思熟慮的。而超出了這一級別的改動,則只能被視爲「重寫」。
重構是一個過程,而重寫不是
重構是一個過程。咱們強調這一點,是爲了不一個誤區:咱們在按照設計進行一次重構後,可能會覺得重構已經結束了。然而,真實的狀況是,重構是沒有終點的,除非一個系統隨着業務價值降低而被徹底放棄維護。開發人員應該持續地發現代碼的不足,而且設法優化這些不足。哪怕咱們不能立刻實施這些優化,咱們也必須清晰地意識到,這是咱們應該作的,重要而不緊急的事情。一方面,這是爲了軟件質量的可持續維護,另外一方面,也是爲了保證開發人員對於「壞味道」的嗅覺。
而重寫則不必定能夠被視爲過程。重寫面向的是功能以上範圍的變更,對外部特性的並不封閉(甚至在工做中常常是經過外部引用資源的修改來達到優化功能的目的),所以客觀上也難以阻止錯誤引入,並且測試上也沒法徹底覆蓋。所以對於重寫,必須是有計劃性的,同時只要咱們進行重寫,咱們就必須爲錯誤的引入作好準備,功能的補償、數據與日誌的監控都必不可少。