天天看點

垃圾代碼和優質代碼的差別?

幾個業務場景中的重構示例

請求順序依賴

在這種場景中,首先還是業務的複雜度決定了代碼的複雜度。首先我們來看一個在前端和node都有可能出現的一個簡單的例子:

我們有 A, B, C, D 四個請求擷取資料的函數(函數自己實作), C 依賴 B 的結果,D 依賴 ABC 的結果,最終輸出 D 的結果。

錯誤示例

垃圾代碼和優質代碼的差別?

雖然這個代碼是故意寫成這樣的,不過确實也有在一些初學者身上看到過。這份代碼還是能正确給出結果的,但是寫法醜陋,回調地獄。如果後來人不進行重構,還有請求依賴,得繼續回調嵌套。性能太差,沒有考慮 A 和 B 實際上是可以并發的。

這裡介紹了一下最原始的 callback ... 中間大家可以去回顧一下 整個 ES2015+ ,callback (async.js) --> Promise --> generator + co --> async + await 的進化過程。其實是從原生的文法層面不斷去簡化和增強我們對于異步的控制能力。

下面直接給目前階段原生提供的終極方案:基于 Promise + async/await

正确示例

垃圾代碼和優質代碼的差別?

我們重新思考了一下上面的問題,理清楚了邏輯順序的依賴。并且用最新的文法。

使用 Promise.all 結合 async/await 的形式,考慮了并發和串行,寫法簡潔,達到了在示例要求下的最快方案。解決了無限嵌套的問題。這是跟随語言進化本身帶給我們可以進行的優化。

但又不僅僅如此。我們将問題進行歸類 将 B,C 有依賴順序的請求,抽離出單獨的函數。讓他們去處理自身的邏輯。這個點我們稍後再提。

折磨人的 if else

可能存在下面一些問題

過多的嵌套

邏輯處理備援

沒有做好防禦程式設計(錯誤處理

直接來一個代碼例子,這是一個擷取背景顔色的方法,但是随着業務的不斷變化,背景顔色的來源越來越多,在一些業務人員的處理下可能是這樣的:

垃圾代碼和優質代碼的差別?

相信你在讀上面的代碼的時候是極為痛苦的,想要一目了然的知道最終會進入哪個分支,基本不可能。于是基于下面兩個原則

合理的抽取成函數

錯誤優先傳回

有了一個基礎版本的重構:

垃圾代碼和優質代碼的差別?

可以看到整個邏輯,經過了重新梳理。拆分成了三個函數,子方法分别去處理對應層級的邏輯,由一個主方法負責排程。整體都變得一目了然了。

當然,在我們基于上面的原則進行重構之後,這個代碼有沒有問題呢?當然有。可以看到我們這三個函數,都依賴了全局變量。函數本身就不純了。如果是全局的問題,還是不易于排查。

我們可以将其修改為純函數,讓這一份代碼易于了解和測試。

以一個函數的修改為示例:我們将 全局變量變成了參數,隻需要在調用的時候,将全局變量傳入即可,但是這樣,我們得到了一個純函數。

垃圾代碼和優質代碼的差別?

為什麼會在這裡特别強調這個點呢,其實在函數式程式設計中的一個最基礎的問題那就是純函數。隻有這樣輸入輸出才是可被觀測的,一個輸入一定會有一個輸出。也隻有通過這樣的方式,才能讓系統中非純的函數越來越少。讓代碼變得更易于測試。

當然作為我們如果以重構的角度去思考的話,我們還需要關注到這個點:

垃圾代碼和優質代碼的差別?

這裡的邏輯會将會 最後一個被比對到的資料,設定為 bgColor 。(我們都知道 find indexOf 等基本都是從前比對。)是否真的是業務的需求呢?

可以看到将業務代碼寫好/重構的過程中其實也是對業務邏輯和業務了解的再一次提升。不論是抽取成函數還是錯誤優先傳回的設計,這其實也都是可以解決這樣一個問題:能在不去讀懂全局的情況下,了解某一個區域的細節邏輯,也就做到了讓代碼易于了解和修改。

... 這裡的代碼即便是經過這樣的重構後,依然有可以考慮進一步優化的空間,比如函數與參數的命名,完整的測試用例等等,受限于文章篇幅,暫不展開說明。

一些代碼中可能存在的其他問題

邏輯耦合在視圖層。

a === 'a' && b ==='b' && c==='c' && d ==='d'?

...

:null

元件複用,函數複用,不封裝,代碼重複。

函數功能不單一,一個函數處理太多職責。且這些職責沒有任何關聯,但是都耦合在同一個區塊内。

參數清單混亂,有做好防禦程式設計,不處理錯誤(接口錯誤,逾時,重複送出等等

魔法數字,魔法字元串,且沒說明。

糟糕資料結構 / 糟糕命名 (其實上面的具體代碼示例也存在)

關于優化代碼的心理準備

首先來說一下為什麼會說需要優化代碼?

技術追求。

公司要求,線上有系統在用。有使用者在用,不寫好出問題實際上苦的還是自己。

團隊協作,我不好好寫,團隊成員其他人也不好好寫,惡性循環苦的還是自己。

快速疊代。系統需要不斷的增加新功能。必須要寫好代碼才能做到。

其他人的看法,怕别人覺得自己技術能力差... xxxx....

那麼就會有下面這些要求:

易于了解系統的架構

易于了解系統的生命周期與執行流程

易于了解每一個函數的作用

易于了解函數之間是如何調用與傳遞的(輸入輸出)

易于了解變量的含義,表達式的含義。

易于擴充...

最終實際上又回到了寫出來的代碼應該是 整潔的代碼,要使代碼易于了解/修改/測試。(這裡其實大部分時候,都隐含了一個人員協作的條件在裡面,是以,既要寫好代碼,又不能過度封裝,讓團隊其他成員看不懂(當然如果确實有些人經驗不夠,那麼是他自身的問題,需要他自己去加強。))

一些建議

更加清晰的去了解業務,去思考可能的變化。思考和設計清楚再動手。

看一些開源項目與業界最佳實踐,明白什麼樣的是好代碼,什麼樣的是不好的代碼。

建立明白代碼雖然是給計算機運作的,但最終還是人看的。不僅僅是沒有 bug 就行了,這樣的心智模型。

建立業務與代碼品質同等重要的思考模型。避免因為時間導緻的不得不這麼寫的代碼。

明白 code review 本身可能能發現和指出來一些問題,但最終的落實還的靠自己,不能變成形式,而是需要融合成自身的思考。

使用錯誤優先原則。盡可能的讓出錯的先傳回, 這樣後面就會得到幹淨的代碼。(寫代碼的時候,不僅僅正向,反向的判斷也需要思考)

合理的拆分成獨立的函數。明确輸入輸出,錯誤處理等在函數内部的處理。(比如在一些場景中确實會存在大量邏輯判斷,首先就要思考在判斷内部的語句是否能被歸類與拆分出去)

對于多種狀态的判斷與組合,可以使用 組合狀态表 (map表)狀态機等模式

學習設計模式與重構等相關知識。

重構!!隻要你覺得這個地方有問題了,那就不要等到以後。以後往往就是再也不。

結束

說到這可能會有一種戛然而止的感覺。在這一篇文章裡面,我們首先以兩個優化代碼的具體執行個體為引子,讓大家明白了一些業務代碼的優化思路。在之後從列舉了一些其他可能出現的錯誤,以及是優化代碼的心理準備和理論指導。

其實都是希望大家能夠在業務中去發現問題,再去思考如何解決問題,因為說了那麼多,到底能不把代碼寫好。還是得靠自己。

繼續閱讀