針針見血:如何消除JS中的程式碼異味

發表於2016-07-05

本文羅列JavaScript程式碼中常見的程式碼壞味道,如臨時定時器,雙向資料繫結的坑,複雜的分支語句,重複賦值等,對它們進行分析如現場還原,糟糕程式碼回顧,問題診斷和識別(通過ESlint或其他工具),程式碼重構方案,給出了怎麼寫好程式碼的一手經驗~

繞來繞去,很燒腦

問題現場:

如果單詞以子音開頭(或子音集),把它剩餘的步伐移到前面,並且新增上『ay』如pig -> igpay
如果單詞以母音開頭,保持順序但是在結尾加上『way』如,egg->eggway等

糟糕程式碼:

問題在哪:

  • 太多語句
  • 太多巢狀
  • 太高複雜度

檢測出問題:

關於Lint的配置項:如最大語句數,複雜度,最大巢狀數,最大長度,最多傳參,最多巢狀回撥

測試先行:

重構後程式碼:

資料對比:

max-statements: 16 → 6
max-depth: 5 → 2
complexity: 7 → 3
max-len: 65 → 73
max-params: 1 → 2
max-nested-callbacks: 0 → 1

相關資源:

jshint – http://jshint.com/
eslint – http://eslint.org/
jscomplexity – http://jscomplexity.org/
escomplex – https://github.com/philbooth/escomplex
jasmine – http://jasmine.github.io/

貼上複製

問題現場:

我們需要實現如下的效果

糟糕的程式碼:

問題出在哪:

因為我們在貼上複製!!

檢測出問題:

檢查出貼上複製和結構類似的程式碼片段 – jsinspect
https://github.com/danielstjules

從你的JS,TypeScript,C#,Ruby,CSS,HTML等原始碼中找到貼上複製的部分 – JSCPD
https://github.com/kucherenko/jscpd

重構後程式碼:

  • Let’s pull out the random color portion…
  • Let’s pull out the weird [].forEach.call portion…
  • Let’s try to go further…

複雜的分支語句

糟糕的程式碼:

問題出在哪:

違反了 open/close 原則:

軟體元素(類,模組和方法等)應該易於被開啟擴充套件,但是除了本身不要多於的修改。既程式碼本身可以允許它的行為被擴充套件,但是不要修改原始碼

可以使用諸如檢查:
no-switch – disallow the use of the switch statement
no-complex-switch-case – disallow use of complex switch statements

重構後程式碼:

這時候新增一個程式碼就不像之前那樣該原先的switch,直到它又長又臭,還容易把之前的程式碼邏輯broken掉。

魔法數字/字串的壞味道

糟糕程式碼:

如上面看到的,如Magic Strings,對於諸如Triangle,Square這些就是特殊字串。

問題出在哪:

這些魔法數字和字串是直接寫死在程式碼中,不容易修改和閱讀。注入password.length > 9,這裡面的9是指 MAX_PASSWORD_SIZE ,這樣先定義後使用更清晰。同時如果多個地方需要這個判斷規則,也可以避免多次修改類似9這樣的數字

https://en.wikipedia.org/wiki/Magic_number_(programming)
http://stackoverflow.com/questions/47882/what-is-a-magic-number-and-why-is-it-bad

重構後程式碼:

1 通過物件

2 通過 const 和 symbols

程式碼深淵

糟糕程式碼:

問題出在哪:

奇奇怪怪的 self /that/_this 等

使用一下的eslint:

  • no-this-assign (eslint-plugin-smells)
  • consistent-this
  • no-extra-bind

重構後程式碼:

利用Function.bind, 2nd parameter of forEach, es6

脆裂的字元拼接

糟糕程式碼:

問題出在哪:

程式碼很醜陋,也很囉嗦,不直觀。

使用 ES6的模板字串(字串插值和多行)
很多工具和框架也都提供了響應的支援,如lodash/underscore,angular,react 等

  • no-complex-string-concat

重構後程式碼:

jQuery詢問

糟糕程式碼:

問題出在哪:

太多的鏈式呼叫

重構後程式碼

臨時定時器

糟糕程式碼:

問題出在哪:

out of sync timer 不能確認時序和執行

重構後程式碼:

等 3s 去執行timer(setTimeout),然後呼叫 someLongProcess (long process: random time ),接著在迴圈
使用setInterval fn(其中在進行 long process),還是通過callback傳遞來反覆setTimeout(timer, )

重複賦值

糟糕程式碼:

問題出在哪:

有些重複和囉嗦

eslint-plugin-smells

  • no-reassign

重構後程式碼:

1 巢狀的函式呼叫
2 forEach
3 reduce
4 flow

不合理的情報

糟糕程式碼:

問題出在哪:

依賴被緊緊的耦合了
相互呼叫,耦合!如 product 和 shoppingCart 關係

重構後程式碼:

1 dependency injection 依賴注入
2 訊息經紀人broker

不斷的互動呼叫

糟糕程式碼:

問題出在哪:

會造成卡頓,多餘的計算等

重構後程式碼:

throttle 和 debounce

匿名演算法

糟糕程式碼:

問題出在哪:

匿名函式是個好東西,但是給函式命名可以幫助我們:

  • Stack Trace(結合Devtools,跟容易debug)
  • Dereferencing
  • Code Reuse

重構後程式碼:

未明確的執行

糟糕程式碼:

明確觸發時機而不是,寫在 domready

問題出在哪:

很難做單元測試

重構後程式碼:

利用單例模組,加上構建器函式
單例模式(單例有個init 方法,來Kick off) & 建構函式(new Application() -> 在原來的建構函式中Kick off your code!)

雙向資料繫結

糟糕程式碼:

隨便看看你們手頭的MVVM專案(如Angular的等)

問題出在哪:

很難定位執行順序和資料流(Hard to track execution & data flow )
(也是Angular1.x被大力吐槽的地方,被React的Flux)

重構後程式碼:

方案: flux(action, dispatcher, store->view)

React Flux
An Angular2 Todo App: First look at App Development in Angular2

結語

更多的lint規則,在npm上搜尋 eslint-plugin 查詢

相關文章