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