各位程式老司機對重構肯定不會陌生,程式設計師的工作離不開重構。那麼重構是已經“飛入尋常百姓家”的普通技術能力,還是看起來高大上的殺器?
對於一項技術你並不是天生就會而是需要持續學習的,很多人對重構的認識停留在 DevTools 自帶的 Refactor 工具和搜尋引擎出來的幾篇文章,但是這樣得到的知識並不夠完善和系統。所以當指點江山、慷慨激昂的重構時,或許你還不會重構。
這個系列文章會記錄關於這本書的讀書筆記,自己的思考都會在段落前面備註(思考:)。
對程式碼要有敬畏,教條是教條,重要的是自己的思考、實踐和從中收穫(與程式碼的一種默契、與這種場景的合拍,找到一種知音的感覺)。
一、重構、第一個案例
定義:在不改變軟體可觀察行為的前提下改善其內部結構,提高其可理解性、降低其修改成本。本質上說重構就是在程式碼寫好之後改進它的設計。
想真正讓重構技術發揮威力,就必須做到“不需瞭解軟體行為”――聽起來很荒謬,但事實如此。如果一段程式碼能讓你容易瞭解其行為,說明它還不是那麼迫切需要被重構。
那些最需要重構的程式碼,你只能看到其中的“壞味道”,接著選擇對應的重構手法來消除這些“壞味道”,然後才有可能理解它的行為。而這整個過程之所以可行,全賴你在腦子裡記錄著一份“壞味道”與重構手法的對應表。
1.1 起點
如果你發現自己需要為程式新增一個特性,而程式碼結構使你無法很方便地達成目的,那就先重構那個程式,使特性的新增比較容易進行,然後再新增特性。
思考:發現痛點果斷重構。重構分大小,不要把重構想象成都是龐大、曠日持久的工程而不願開始,每天甚至每個小時都可以完成一項小重構。
1.2 重構第一步
重構前,先檢查自己是否有一套可靠的測試機制,這些測試必須有自我檢驗能力。
更改變數名是否值得?絕對值得,好的程式碼應該清楚表達出自己的功能,變數名稱是程式碼清晰的關鍵。
一個任何一個傻瓜都能寫出計算機可以理解的程式碼。唯有寫出人類容易理解的程式碼,才是優秀的程式設計師。
重構與效能衝突:重構時不必擔心效能,優化時才需要在意,但那時你已經處於有利的位置,有更多選擇可以完成有效優化。
思考:
- 重構之前保證有可靠的測試機制:重構只改變軟體內部而不改變軟體可觀察的行為,重構之後的功能要向重構之前對齊;
- 好的程式碼自己就是註釋;
- 程式碼更多是讓人讀而不是讓機器執行的;
- 重構階段不能因為可能的效能問題降低了重構的積極性,完成重構可以讓優化有更多選擇以及更容易的開展;
- 重構與效能優化出發點不同:重構為了更易於理解和修改,效能優化為了所需效能往往會使程式碼較難理解;
1.3 重構的節奏
這個例子給我們最大的啟發是重構的節奏:測試、小修改、測試、小修改、測試、小修改……正是這種節奏讓重構得以快速而安全地前進。
思考:重構的前提是不改變軟體行為,意味著不能因為重構程式碼而引入 bug。相對於大刀闊斧的程式碼刪改,“小步快跑,快速驗證”的節奏明顯更可控。
二、重構原則
2.1、為何重構
程式碼結構的流失是累積性的,越難看出程式碼所代表的設計意圖,就越難保護其中設計,於是該設計就腐敗的越快。經常性的重構可以幫助程式碼維持自己該有的形態。
改進設計的一個重要方向就是消除重複程式碼。 這個動作的重要性在於方便未來的修改。程式碼量減少並不會使系統執行更快。然而程式碼量減少將使未來可能的程式修改動作容易得多。程式碼越多,正確的修改就越困難,因為有更多程式碼需要理解。如果消除重複程式碼,你就可以確定所有事物和行為在程式碼中只表述一次,這正是優秀設計的根本。
所謂程式設計,很大程度上就是與計算機交談:你編寫程式碼告訴計算機做什麼事,它的響應則是精確按照你的指示行動。你得及時填補“想要它做什麼”和“告訴它做什麼”之間的縫隙。這種程式設計模式的核心就是“準確說出我所要的”。除了計算機外,你的原始碼還有其他讀者;幾個月之後可能會有另一位程式設計師嘗試讀懂你的程式碼並做一些修改:我們很容易忘記這第二位讀者,但他才是最重要的,計算機是否多花了幾個小時來編譯,又有什麼關係呢?如果一個程式設計師花費一週時間來修改某段程式碼,那才要命呢――如果他理解了你的程式碼,這個修改原本只需一小時。
思考:重構讓軟體更容易理解、更容易交接、更容易被自己回憶、更容易被別人修改,可以更好的提升人效。
我絕對相信:良好的設計是快速開發的根本――事實上,擁有良好設計才可能做到快速開發。如果沒有良好設計,或許某一段時間內你的進展迅速,但惡劣的設計很快就讓你的速度慢下來。 你會把時間花在除錯上面,無法新增新功能。修改時間越來越長,因為你必須花越來越多的時間去理解系統、尋找重複程式碼。隨著你給最初程式打上一個又一個的補丁,新特性需要更多程式碼才能實現。真是個惡性迴圈。
思考:良好設計是維持軟體開發速度的根本。重構可以幫助你更快速地持續開發軟體,因為它阻止系統腐敗變質、防止程式碼變得難以理解、防止變得難以新增新功能,它甚至還可以提高設計質量。
2.2、何時重構
事不過三,三則重構。第三次做同樣的事、遇到同樣的麻煩,就是需要重構的時候了。
新增功能時重構,程式碼的設計無法幫助我輕鬆新增我所需要的特性:我看著設計,然後對自己說:“如果用某種方式來設計,新增特性會簡單得多。”這種情況下我不會因為自己過去的錯誤而懊惱――我用重構彌補它。之所以這麼做,部分原因是為了讓未來增加新特性時能夠更輕鬆一些,但最主要的原因還是:我發現這是最快捷的途徑。重構是一個快速流暢的過程,一旦完成重構,新特性的新增就會再快速、再流暢。
思考:新增功能時重構既是彌補的一次機會,同時同樣的時間消耗卻多了額外的產出:更容易維護的設計。
複審程式碼時重構
- 複審程式碼有助於知識的傳播,有利於程式碼被編寫者之外的人理解;
- 複審程式碼可以得到別人角度的建議,然後動手實現,避免主觀誤判;
- 複審團隊需要精煉,就一個審查者和一個原作者。較大的專案可以通過UML圖去展示程式碼的邏輯;
對於程式碼我們的希望:
- 容易閱讀;
- 所有邏輯都只有唯一地點指定;
- 新的改動不會危及現有行為;
- 儘可能簡單表達邏輯;
2.3 怎麼對經理說
- 不要告訴經理:經理是進度驅動,就是要求開發者儘快完成任務。而對於我來說最快完成任務的方式就是先重構。
- 很多時候重構都為程式引入間接層。把大型物件拆分成小物件,把大型函式拆分為小型函式。
- 允許邏輯共享:一個函式在不同地點被呼叫。子類共享超類的方法;
- 分開解釋意圖和實現:通過類名和函式名解釋自己的意圖;
- 隔離變化:在不同地方使用同一個物件,需要修改一處邏輯,那麼可以做出子類,並在需要的時候修改這個子類;
- 封裝條件邏輯:運用多型。將條件邏輯轉化為訊息模式;
- 減少間接層:當間接層只在一處使用,那麼需要將其消除。
2.4 重構的難題
- 修改介面
- 對於已經發布的介面需要可能需要維護舊介面和新介面,用 deprecated 修飾舊介面;
- 不釋出新介面,在舊介面中呼叫新介面;
- 假如新介面丟擲編譯時異常,那麼可以在舊介面中呼叫新介面並將編譯時異常轉化為執行時異常;
不要過早釋出介面,請修改你的 api 所有權,使重構更順暢。
- 何時不重構
- 重構之前,程式碼必須能夠在大部分情況下正常執行,不然就不應該重構,而應該是重寫;
- 到了 Deadline,應該避免重構。通常意味著你錯過了早該進行重構的時間;
重寫的一個清楚訊號是:現有程式碼根本不能正常運作。重構與重寫的區別是:重構之前,程式碼在大多數情況下正常運作。
2.5 重構與設計
- 重構與設計是彼此互補的;
- 預先設計是必須,預先設計不可能做到完全正確,隨著對問題的逐漸深入,通過重構可以改善程式的質量;
- 重構減輕了設計的難度和壓力,在程式不斷修改的過程逐步完善程式的設計;
思考:重構與設計二者不是互斥的關係,而是不同的專案階段不同的選擇、重點。
2.6 重構與效能
- 重構是有可能導致程式執行變慢的;
- 除了對實時有嚴格要求的程式,編寫快速軟體的祕訣是:首先寫出可調的程式,然後調整它以達到足夠的速度;
- 經過分析大部分程式的大半部分時間是執行在一小半程式碼上,所以對所有程式碼一視同仁是錯誤的;
- 效能優化放在開發的後期,通過分析工具找出消耗大量時間空間的地方,然後集中精力優化這些地方;
思考:重構與效能在某些場景下是衝突的:程式碼清晰明瞭是為了讓程式設計師(人)來看的,減少的是程式設計師的時間成本;而效能則是表現在機器上。人容易看的程式碼一些場景下效能可能有瓶頸(想一想你寫過的為了實現某項效能指標而改過的程式碼),那麼孰重孰輕?首先寫出可調的程式,然後調整它以達到足夠的速度。
廣告時間
今日頭條各Android客戶端團隊招人火爆進行中,各個級別和應屆實習生都需要,業務增長快、日活高、挑戰大、待遇給力,各位大佬走過路過千萬不要錯過!
本科以上學歷、對技術有熱情,歡迎加我的微信詳聊:KOBE8242011