圈複雜度那些事兒-前端程式碼質量系列文章(二)

阿里巴巴TXD發表於2018-04-24

Are You Smart Enough To Debug Your Own Code?

圈複雜度那些事兒-前端程式碼質量系列文章(二)


Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.


上面這句話的意思是:除錯的難度相當於寫程式碼的兩倍。因此,如果你寫出了你自認為最聰明巧妙的程式碼,那麼你將不具備擁有足夠的智商去除錯它。根據這段話的意思,我們不應該去寫出“自作聰明”的程式碼,否則的話我們自己都沒有能力維護。應該寫出淺顯易懂,剛畢業的實習生都能看懂的程式碼才是好程式碼。


01 圈複雜度定義

怎麼才能寫出淺顯易懂的程式碼就不得不提軟體工程質量度量方法中一個重要的概念——圈複雜度。

圈複雜度(Cyclomatic complexity,簡寫CC)也稱為條件複雜度,是一種程式碼複雜度的衡量標準。由托馬斯·J·麥凱布(Thomas J. McCabe, Sr.)於1976年提出,用來表示程式的複雜度。它可以用來衡量一個模組判定結構的複雜程度,數量上表現為獨立現行路徑條數,也可理解為覆蓋所有的可能情況最少使用的測試用例數。

圈複雜度大說明程式程式碼的判斷邏輯複雜,可能質量低且難於測試和維護。有研究表明複雜度和出現缺陷的數量存在強相關性,表明了越複雜的程式碼越可能會出錯。


02 圈複雜度計算

圈複雜度衡量的是程式中線性獨立路徑的數量。

例如:如果程式中不包含控制、判斷、條件語句,那麼複雜度就是 1,因為整個程式只有一條執行路徑;

如果程式包含一條 IF 語句,那麼就會有兩條路徑來執行完整個程式,所以這時候的複雜度就是 2;

兩個巢狀的 IF 語句,或者包含兩個判斷條件的一個 IF 語句,複雜度就是 2 * 2 = 4。

更加具體的情況看下圖:


圈複雜度的計算方法


圈複雜度那些事兒-前端程式碼質量系列文章(二)

  1. 圈複雜度可以通過程式控制流圖計算,公式為:V(G) = e + 2 - n

  e : 控制流圖中邊的數量

  n : 控制流圖中節點的數量

  1. 圈複雜度對應程式控制流圖中從起點到所有終點的路徑的條數,所以也可以通過數路徑的方式獲得圈複雜度。


03 降低複雜度


那麼問題來了,我們如何降低程式碼複雜度呢?如果你想寫出易讀且可測試的程式碼,可以使用能夠有效降低程式碼複雜度的利器——函數語言程式設計。

我們已經瞭解了圈複雜度指的是一段程式執行分支的可能性數量。而函數語言程式設計就是儘量避免使用所有迴圈語句這種會增加程式碼執行分支的語句。函數語言程式設計是一種指導我們如何編寫程式的方法論,主要思想是把運算過程儘量寫成一系列巢狀的函式呼叫。

一種比較極端的觀點任務數學是物理世界的根基,一切問題都是數學問題,世間萬物都可以用數學函式去表示。電腦的運算也可以視為數學上的函式計算,所以我們的程式碼其實也都可以歸納化簡成為一系列數學表示式。

函數語言程式設計有要求只用"表示式",不用程式導向的"語句"。表示式和語句的區別是表示式總有一個返回值,而語句表示執行了某種操作。只使用表示式的函式我們可以成為“純函式”,純函式的好處是不論函式被執行多少次,都不會產出任何的副作用,因為它不會去修改中間狀態。

由於我們使用函數語言程式設計,我們就可以用表示式去代替各種條件判斷語句,從而降低程式碼的圈複雜度,使我們的程式碼可讀易測試。在函數語言程式設計語言裡沒有 for 迴圈,因為這些邏輯意味著有狀態的改變。相替代的是,這種迴圈邏輯在函數語言程式設計語言裡是通過遞迴、把函式當成引數傳遞的方式,也就是高階函式實現的。

JavaScript雖然在設計的時候不是函式式語言,但是我們可以通過 lodash,underscore等方法庫來讓我們的程式更加函式式。函數語言程式設計是我最為推崇的有效降低程式碼複雜度的程式設計方法。但是具體關於函數語言程式設計的內容不屬於本篇的範疇,在這裡不再展開闡述。

除了函數語言程式設計,我們還能使用一些更為傳統的方式來降低程式碼複雜度。常見的方法有:

  • 提取函式 - 將獨立業務或模組程式碼獨立出來,封裝提煉為函式,並通過函式名詮釋程式碼作用,提高程式碼可讀性。當一個函式過於複雜時就應該拆分為多個負責獨立功能的子函式。

  • 替換演算法 - 複雜演算法會導致bug可能性的增加及可理解性/可維護性。

  • 合併條件式 - 把複雜的條件表示式,使用函式進行封裝。舉個例子:

if(data.code === 200 && data.id && data.list.length > 2) {
    db.insert(data);
} 複製程式碼

我們可以將條件表示式封裝為函式:

if(isValidate(data)) {
    db.insert(data);
}複製程式碼


通過以上這些方法我們可以使自己的程式碼保持一個較低的程式碼複雜度。在我們寫程式碼的時候我們記住要讓自己的程式碼 Stay Simple Stay Foolish 。一個有助於你記住這個原則的辦法是“寫程式時時刻記著,這個將來要維護你寫的程式的人是一個有嚴重暴力傾向,並且知道你住在哪裡的精神變態者”。


圈複雜度那些事兒-前端程式碼質量系列文章(二)



圈複雜度那些事兒-前端程式碼質量系列文章(二)

關注微信公眾號檢視更多原創內容


相關文章