DevUI是一支兼具設計視角和工程視角的團隊,服務於華爲雲 DevCloud平臺和華爲內部數箇中後臺系統,服務於設計師和前端工程師。
官方網站: devui.design
Ng組件庫: ng-devui(歡迎Star)
重構,是咱們開發過程當中不可避免須要進行的一項工做。重構代碼,以適配當前模塊設計之初未考慮到的多樣化場景,並增長模塊的可維護性、健壯性、可測試性。那麼,如何明確重構的方向,以及量化重構的結果呢?前端
代碼圈複雜度(Cyclomatic complexity,CC)能夠是一個供選擇的指標。git
圈複雜度(Cyclomatic complexity,CC)也稱爲條件複雜度或循環複雜度,是一種軟件度量,是由老托馬斯·J·麥凱布(Thomas J. McCabe, Sr.)在1976年提出,用來表示程序的複雜度,其符號爲VG或是M。圈複雜度即程序的源代碼中線性獨立路徑的個數。github
下表爲模塊(函數)圈複雜度與代碼情況的一個基本對照表。除了表中給出的代碼情況、可測性、維護成本等指標外,圈複雜度高的模塊(函數),也對應着高軟件複雜度、低內聚、高風險、低可讀性。咱們要下降模塊(函數)的圈複雜度,就是要下降其軟件複雜度、增長內聚性、減小可能出現的軟件缺陷個數、加強可測試性、可讀性。算法
麥凱布提出圈複雜度時,其原始目的之一就是但願在軟件開發過程當中就限制其複雜度。他建議程序設計者需計算其開發模塊的複雜度,若一模塊的圈複雜度超過10,需再分割爲更小的模塊。NIST(國家標準技術研究所)的結構化測試方法論已此做法略做調整,在一些特定情形下,模塊圈複雜度上限放寬到15會比較合適。此方法論也認可有些特殊情形下,模塊的複雜度須要超過上述的上限,其建議爲「模塊的循環複雜度需在上限範圍之內,不然需提供書面數據,說明爲什麼此模塊循環複雜度有必要超過上限。」segmentfault
優秀的代碼模塊間老是低耦合,高內聚的。能夠預期一個複雜度較高模塊的內聚性會比較低,至少不會到功能內聚性的程度。一個有高複雜度及低內聚性的模塊中會有許多的決策點,這類的模塊多半運行超過一個明肯定義的任務,所以內聚性較低。前端工程師
許多研究指出模塊(函數)的圈複雜度和其中的缺陷個數有相關性,許多這類研究發現圈複雜度和缺陷個數有高度的正相關:圈複雜度最高的模塊及方法,其中的缺陷個數也最多。框架
一個圈複雜度高的模塊(函數),由下文中將描述到的計算方法來看,必然會有更多的運行分支,要對這樣的模塊進行如單元測試用例的編寫,將會十分複雜,而且後期用例維護也是一個問題。函數
代碼可讀性是大型項目與團隊協做間必需要考慮的一個因素。圈複雜度高的模塊(函數),隨着邏輯複雜度的增長,代碼可讀性也將下降,不利於成員間相互協做與後期維護。工具
一段程序的圈複雜度是其線性獨立路徑的數量。若程序中沒有像IF指令或FOR循環的控制流程,由於程序中只有一個路徑,其圈複雜度爲1,若程序中有一個IF指令,會有二個不一樣路徑,分別對應IF條件成立及不成立的情形,所以圈複雜度爲2。post
數學上,一個結構化程序的圈複雜度是利用程序的控制流圖來定義,控制流圖是一個有向圖,圖中的節點爲程序的基礎模塊,若一個模塊結束後,可能會運行另外一個模塊,則用箭頭連接二個模塊,並標示可能的運行順序。圈複雜度M能夠用下式定義:
M = E − N + 2P
其中
E 爲圖中邊的個數
N 爲圖中節點的個數
P 爲鏈接組件的個數
一款VSCode插件,用於度量TS、JS代碼圈複雜度。
eslint
也能夠配置關於圈複雜度的規則,如:
rules: { complexity: [ 'error', { max: 15 } ] }
表明了當前每一個函數
的最高圈複雜度
爲15,不然eslint
將給出錯誤提示
。
一款開源的代碼圈複雜度檢測工具(github:https://github.com/ConardLi/awesome-cli),能夠生成當前項目下代碼圈複雜度報告。
要下降圈複雜度,咱們就須要瞭解是哪些語句哪些結構致使了咱們複雜度的增長,如下爲常見結構圈複雜度說明。
順序結構複雜度爲1。
例:
function func() { let a = 1, b = 1, c; c = a * b; }
如上代碼,func函數內部爲順序結構,其控制流圖以下:
邊:1,點:2,連通分支:1,
圈複雜度:
M = 1 - 2 + 2 * 1 = 1
每增長一個分支,複雜度增長1,&& 、|| 運算也爲一個分支。
例:
function func() { let a = 1, b = 1, c; if (a = 1) { c = a + b; } else { c = a - b; } }
邊:4,點:4,連通分支:1,
圈複雜度:
M = 4 - 4 + 2 * 1 = 2
增長一個循環結構,複雜度增長1。、
例:
function func() { let a = 1, b = 1, c = 2; for (let i = 1; i < 10; i++) { b += a; } c = a + b; }
邊:4,點:4,連通分支:1,
圈複雜度:
M = 4 - 4 + 2 * 1 = 2
從理論上來說,return並不會增長當前模塊圈複雜度,但在某些度量工具看來,一條return語句將增長總體程序的一條路徑,而且若是提早返回,將增長程序的不肯定性,因此在大多數計算工具中,每增長一條return語句,複雜度將加1。
既然是下降一個模塊(函數)圈複雜度,那麼對於複雜度極高的函數,首先須要進行就是功能的提煉與函數拆分,每一個函數職責要單一。
例:
function add(a, b) { let tempA; if (a === 10) { tempA = 11; } else if (a === 12) { tempA = 12; } let tempB; if (b === 10) { tempB = 13; } else if (b === 12) { tempB = 12; } return tempB + tempA; }
重構爲:
function add(a, b) { return calcA(a) + calcB(b); } function calcA(a) { if (a === 10) { return 11; } else if (a === 12) { return 12; } } function calcB(b) { if (b === 10) { return 13; } else if (b === 12) { return 12; } }
不只下降了add函數圈複雜度,而且代碼結構更加清晰,增長了可讀性,同時還增長了當前代碼可維護性、可測試性。
固然,過猶不及,咱們的目標爲提煉函數,保持函數單一職責,不能爲了下降圈複雜度而進行暴力拆分。
從圈複雜度計算上來看,條件、循環分支均會增長模塊圈複雜度。從某些程度上,複雜的條件與循環結構是可優化,減小沒必要要結構,從而下降圈複雜度。
例:
let a = 'a', c; if (a === 'a') { c = a + 1; } else if (a === 'b') { c = a + 2; } else if (a === 'c') { c = a + 3; } else if (a === 'd') { c = a + 4; } return c;
重構爲:
let a = 'a', c; let conditionMap = { a: 1, b: 2, c: 3, d: 4 } c = a + conditionMap[a]; return c;
消除了全部條件分支,從而大幅下降了當前函數圈複雜度。
邏輯計算也將增長圈複雜度,優化一些結構複雜的邏輯表達式,減小沒必要要的邏輯判斷,也將必定程度上下降圈複雜度。
例:
a && b || a && c
可進行簡單優化爲:
a && (b || c)
從而使表達式圈複雜度下降1。
單從下降圈複雜度上來看,因爲當前大多數圈複雜度計算工具將對return個數進行計算,故若要針對這些工具衡量規則進行優化,減小return語句個數也爲一種方式。
例:
let a = 1, b = 1; if (a = 1) { return a + b; } else { return a - b; }
重構爲:
let a = 1, b = 1, c; if (a = 1) { c = a + b; } else { c = a - b; } return c;
圈複雜度將下降1。
圈複雜度(Cyclomatic complexity,CC)高的代碼必定不是好代碼,對於咱們代碼好壞的衡量,圈複雜度能夠做爲一個參考指標;能夠經過控制流圖計算圈複雜度;要下降模塊(函數)圈複雜度,提煉拆分函數、優化算法、優化邏輯表達式均爲能夠嘗試的方法。
循環複雜度:https://zh.wikipedia.org/wiki/%E5%BE%AA%E7%92%B0%E8%A4%87%E9%9B%9C%E5%BA%A6
詳解圈複雜度:http://kaelzhang81.github.io/2017/06/18/%E8%AF%A6%E8%A7%A3%E5%9C%88%E5%A4%8D%E6%9D%82%E5%BA%A6/
前端代碼質量-圈複雜度原理和實踐:http://www.javashuo.com/article/p-szqkmqes-hh.html
咱們是DevUI團隊,歡迎來這裏和咱們一塊兒打造優雅高效的人機設計/研發體系。招聘郵箱:muyang2@huawei.com。
文/DevUI 砰砰砰砰
往期文章推薦