最近正在重構項目,並且正在看《重構》,在實踐的同時總結了一些點,或許能給你一些重構或者寫代碼上的一些思考。
我一直認為代碼結構是一個因人而異的事情,很多時候我們其實判斷一個代碼的好壞往往是通過主觀判斷,比如同樣是實現一個功能,100 行的代碼並非一定比 50 行的差;我們沒有一個合理的標杆去評判。
但是,最近我的想法變了,發現有些代碼一定是毒藥,早點發現他們,往往會對於我們以後需求的修改有莫大的幫助。
命名
如果把整個項目代碼比作是房屋建造,命名就是磚頭,命名的好壞直接決定了你代碼 50% 的可讀性。絕大部分的情況下,讀者應該可以通過你函數的命名,直接瞭解到你這個函數的功能。
要求
- 命名要描述具體意圖而非模糊的操作
- 功能命名不要出現技術名詞
- 整個項目統一命名
命名要描述具體意圖
不好的比如:process、modify...
通常我們需要使用一個動詞+賓語,比如 modifyUsername,processFile
而一個類(對象)的命名,通常使用名詞
功能命名不要出現技術名詞
不好的比如:UserRoleMap
顯然 Map 是一個技術名詞,而這個對象的類型其實往往已經可以清楚的標識這個是個 map 類型。
這裏想要表示的是一個用户角色名稱的映射關係,個人習慣會通常命名為: UserRoleMapping 理解為映射關係
整個項目統一命名
最好在項目建立數據庫的時候就統一命名,特別是針對一些專有名詞的命名,可以建立一個表格。
並且很多英語單詞的非常考究的,小技巧:當你使用翻譯軟件翻譯成一個英文單詞之後,將這個英文單詞再放到搜索引擎裏面再去搜一遍,或者搜索對應圖片,就能知道這個單詞是否真的是你想要的
函數
控制函數長度
書本上有一句話經常被人提到就是:寫的代碼越少,bug 越少,所以要減少函數長度
這句話我是不認可的,有的時候代碼極具的減少,可能會帶來一些意外的操作,因為長度的減少有些時候並不是 等值替換 特別是 python 這種經常可以一行搞定的情況。
但是我認可要控制函數長度,函數長度越短,功能點越集中,閲讀代碼速度越快。很多函數我看一眼命名就知道要完成的功能是什麼,然後測試的時候,只要輸出沒問題,則這個函數就可以直接跳過不看,如果函數長,那麼我必須一行行的去看究竟是哪一個地方出現了問題。
不同的語言不同,函數長度控制限制不同,比如 python 往往就會短一些,java 就會長一些,由於 golang 經常還會寫一些 if err != nil 會更加拖長一些。
PS:個人一般會盡量控制在 35 以內,但未嚴格執行 lint。
缺乏封裝
控制函數長度之後很容易導致的一個問題就是缺乏封裝。你肯定會奇怪了,我都把原來一個 200 行函數拆成 5 個函數了,為什麼你還説缺乏封裝呢?
案例:
function test() {
a = testA()
b = a.testB()
c = b.testC()
...
// 或者寫為
c = a.testB().testC()
}
我也經常會寫這樣的代碼,但是其實隱藏一些細節,才是封裝的精髓。提供一個經常在重構使用的思路:
將一個函數分為三段:前置條件檢查,基本邏輯處理,後置返回值處理
我往往將一個函數分好之後,就會發現,函數中的幾個調用雖然來源不同,但是都是在做同一個事情,職責相同,隱藏其中的細節會對函數有更好的封裝。
控制函數參數長度
之前在 java 的 阿里規範裏面提到 函數的參數數量的控制,超過一定數量就需要封裝成一個類,這個沒有問題,很多人也都能做到。
但是,千萬不要故意把所有的參數封裝為一個對象,特別是業務屬性本來就是不同的,有的時候封裝成兩個對象會更加複用或更加滿足職責單一的要求。
控制嵌套
Cyclomatic complexity CC 複雜度用來描述代碼編寫的一些複雜度,經常使用 else 或者 for 的嵌套會導致複雜度異常的高。
其實控制嵌套的本質,我經常遵循的就是下面這句話:
讓你的主邏輯保持一條直線,能不用 else 就不用,不超過 3 層嵌套
在 golang 中我會使用 gocyclo 工具類檢測函數複雜度。
DRY !!!
Don't Repeat Yourself
不要寫重複代碼,這個原則説説容易,其實做起來真的很難,因為我自己也經常陷入這個泥潭裏面。
但是也不知道是共性還是人性,實現一個類似功能,想到的第一個反應絕對是拷貝 (CV 程序員無疑)。
所以我看完《程序員修煉之道》總結了以下方案供你參考:
- 如果你還在 feat 分支開發,重複吧,沒事的
- 如果你發現一個代碼重複三次,立刻抽離成函數,別猶豫
- 在你提交 PR 或者進行 CR(code review) 的時候,重構重複的部分,別灰心
- 使用 lint 工具來檢查重複代碼
- 有時候,結構也是重複,仔細點
因為在實際情況我在實踐的時候,往往剛剛抽離完成,就發現業務邏輯需要被更改,導致代碼又拆開來的情況,或者需要提供額外的參數。
協同工作的時候一定要 Code Review ,因為大多數情況重複代碼不一定是同一個人寫的,特別是一些工具類。DRY 通常是重構中最容易發現的問題,也最容易被修改,也最容易被程序員犯錯。
四個基本原則
少暴露細節
無論是函數還是類,能私有就私有,能不被外面看到就不被,很多時候,暴露的越多,調用的越多,就會有越多的錯誤。
單一職責
這也是和函數的命名有關,如果一個函數它只做一件事,那麼就做好它命名的這件事,不要做多,職責單一方便閲讀也方便排查問題,也不會產生過多的依賴。
動靜分離
將代碼中一定不會變動的部分和經常會被變動的部分進行分離,特別是一些類和變量的聲明,可以將變化的部分抽離單獨編寫。
開閉原則
開閉原則,對擴展開放,對修改關閉。第一次我知道這個原則的時候很不理解。直到不斷寫代碼的過程中,我漸漸的明白了:
- 修改關閉:既然你不讓修改,那麼你依賴的就是接口,而非實現,接口的參數方法名不變,你就不會修改,你就更不會犯錯。
- 擴展開發:如果你想進行功能擴展,那麼中間的實現可以被改變,通過不同的實現來擴展。
最後的警告
這是血與淚的教訓~ 如果你當前的並不是在業務的開發過程中,而是在一個已經完整的上線或運行的業務上進行重構,請務必添加有必要的單元測試。重構最基本的要求就是保證已有的業務正常運行,而能保證這件事的絕不是程序員口中的“我這樣改和原來一樣”。
因為大多數重構都是沒有 KPI 的,那誰也不想因為重構而背鍋。
總結
重構的基本思路:
- 發現壞味道*第三章
- 抽離/合併/刪除 代碼
- 重新測試
參考
https://zh.m.wikipedia.org/zh-hans/SOLID_(%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%AE%BE%E8%AE%A1)
https://book.douban.com/subject/30468597/
https://book.douban.com/subject/34986245/