博客 / 詳情

返回

重構-改善既有代碼的設計

最近正在重構項目,並且正在看《重構》,在實踐的同時總結了一些點,或許能給你一些重構或者寫代碼上的一些思考。

clean-code

我一直認為代碼結構是一個因人而異的事情,很多時候我們其實判斷一個代碼的好壞往往是通過主觀判斷,比如同樣是實現一個功能,100 行的代碼並非一定比 50 行的差;我們沒有一個合理的標杆去評判。

但是,最近我的想法變了,發現有些代碼一定是毒藥,早點發現他們,往往會對於我們以後需求的修改有莫大的幫助。

命名

如果把整個項目代碼比作是房屋建造,命名就是磚頭,命名的好壞直接決定了你代碼 50% 的可讀性。絕大部分的情況下,讀者應該可以通過你函數的命名,直接瞭解到你這個函數的功能。

要求

  1. 命名要描述具體意圖而非模糊的操作
  2. 功能命名不要出現技術名詞
  3. 整個項目統一命名

命名要描述具體意圖

不好的比如: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 通常是重構中最容易發現的問題,也最容易被修改,也最容易被程序員犯錯。

四個基本原則

少暴露細節

無論是函數還是類,能私有就私有,能不被外面看到就不被,很多時候,暴露的越多,調用的越多,就會有越多的錯誤。

單一職責

這也是和函數的命名有關,如果一個函數它只做一件事,那麼就做好它命名的這件事,不要做多,職責單一方便閲讀也方便排查問題,也不會產生過多的依賴。

動靜分離

將代碼中一定不會變動的部分和經常會被變動的部分進行分離,特別是一些類和變量的聲明,可以將變化的部分抽離單獨編寫。

開閉原則

開閉原則,對擴展開放,對修改關閉。第一次我知道這個原則的時候很不理解。直到不斷寫代碼的過程中,我漸漸的明白了:

  1. 修改關閉:既然你不讓修改,那麼你依賴的就是接口,而非實現,接口的參數方法名不變,你就不會修改,你就更不會犯錯。
  2. 擴展開發:如果你想進行功能擴展,那麼中間的實現可以被改變,通過不同的實現來擴展。

最後的警告

這是血與淚的教訓~ 如果你當前的並不是在業務的開發過程中,而是在一個已經完整的上線或運行的業務上進行重構,請務必添加有必要的單元測試。重構最基本的要求就是保證已有的業務正常運行,而能保證這件事的絕不是程序員口中的“我這樣改和原來一樣”。

因為大多數重構都是沒有 KPI 的,那誰也不想因為重構而背鍋。

總結

重構的基本思路:

  1. 發現壞味道*第三章
  2. 抽離/合併/刪除 代碼
  3. 重新測試

參考

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/

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.