寫出好維護的程式碼 — 什麼是軟體複雜度? 如何降低?

2025年11月19日

💎 加入 E+ 成長計畫 與超過 800+ 位工程師一同在社群成長,並獲得更多深度的軟體前後端學習資源

看到複雜度這個詞,相信大家直觀的會覺得,當今天寫出來的程式碼複雜度是低的,那就會更容易維護。反之,假設今天寫出來的程式碼整體的複雜度很高,那麼相對來講,要維護起來就會比較困難一點。

不過,在有這個直觀的判斷背後,一個更關鍵的問題是「程式碼的複雜度到底是什麼? 我們要怎麼樣去衡量?」。

舉例來講,當今天看到一段程式碼,大家會覺得該程式碼的複雜度是高的還是低的? 為什麼? 你會用什麼樣的方式來衡量,然後做出判斷,說這樣的程式碼複雜度是高,或者這樣的程式碼複雜度是低的?

相信多數人過去在寫資料結構演算法,或解決相關的問題的時候,都會使用時間複雜度或者空間複雜度來去衡量某一個演算法的表現,並且去做出取捨。但回到我們今天的討論的主題,假設寫的程式碼可維護,從維護的角度來看,我們用什麼樣的方式來去衡量程式碼的複雜度呢?

衡量程式碼複雜度的兩個指標

基本上要去衡量程式碼的複雜度,可以有很多不同的方式。但是假設要簡化,用一個最直觀讓大家好理解的方式,這邊提供兩個推薦大家平常在寫程式的時候可以進一步去檢視的指標:

第一個是好讀,第二個是好改。

好讀

所謂的好讀,就是當今天一個對於這個程式碼庫非常陌生、剛進到團隊中要讀這個程式碼庫的新成員,要花多久的時間可以看懂這個程式碼庫當中的程式碼?

假如說今天一個新成員看程式碼要花非常多的時間,那麼這個程式碼的複雜度就會被判為很高。反之假設這個新成員可以非常快速的看懂陌生的程式碼,那我們就會說這個程式碼的複雜度是低的。

好改

好改的角度也是一樣。假設今天某一個未來的維護者要去改動某一段程式碼,會覺得這個程式碼有多難改或多好改?假設是好改的話,我們就會判斷它的複雜度是低的。但假設不好改的話,那程式碼的複雜度就是太高了。

當我們有這兩個可以去協助我們判斷的指標之後,往下我們就可以進一步的去討論,要怎麼樣寫程式碼,會讓程式碼比較好讀? 怎麼樣寫程式碼,會讓程式碼比較容易改?

圈複雜度(Cyclomatic Complexity)

在這個單元,我們會先從比較概括的角度談幾個讓程式碼好讀好改的點。在課程後面的單元,我們會花更多的篇幅來更詳細談一些不同的原則,去協助寫出更好讀、更好改的程式碼。

首先,來談好讀這件事情。事實上在軟體業界,有一個行之有年的衡量的指標叫做圈複雜度。這個圈複雜度能夠協助我們去衡量某一段程式碼它有多複雜,以及對於要維護這段程式碼的讀者來講,這個程式碼是不是好讀的。

複雜度是基於圖(graph)的資料結構,來衡量程式碼的複雜度。在一個圖的資料結構中,會有不同的節點。而當節點越多的時候,整體的圖的複雜度就會變得更高。

假設單單只是看資料結構,可能會比較抽象一點,我們看一個比較具體的案例。

大家把這個圖具體的去延,像我們平常在日常生活當中會使用的地圖,例如東京的地鐵的交通圖。

可以看到,在這個圖的上面,每個站可以當成是一個節點。每一個站可能會連到另外一個站,而有一些比較中樞的站,可能同時會連到四五個不同的站。

可以看到,在一個有非常多不同節點、非常多不同站的地圖上,會覺得有點眼花撩亂,會覺得這個圖非常的複雜。但相比於一些其他城市,可能站點沒有這麼多的地鐵的路線圖,可能就不會覺得像東京的地鐵路線圖這麼複雜。

程式碼中的節點

在有了這個概念之後,我們可以回到程式碼來看。在程式碼當中,其實我們可以有一些不同的點來作為節點。當這些不同的節點越多的時候,對於要閱讀程式碼的人來講,也會變得更加的困難一點、複雜度更高一點。

具體來講,在程式碼當中,我們去衡量圈複雜度的時候,看的節點會是在這邊列出的這些不同的判斷的條件。所以最常見的是 ifelse 這種條件判斷,或者是 forwhiledo while 這種迴圈的判斷。

所以大家可以回顧一下過去自己寫過的程式碼,或者讀過別人寫的程式碼。假設在程式碼當中有非常多的條件判斷,相信讀起來就會比較吃力一點。

所以當今天用圈複雜度在衡量的時候,只要在程式碼當中有多一個 if、多一個 else if,這個複雜度就會加一。又或者有多一個 for 的迴圈、while 的迴圈,複雜度也會加 1。

降低圈複雜度的方法

這時候相信大家一個問題就來了,既然我們有一個衡量圈複雜度的指標,那麼具體我們可以通過什麼樣的方式,來降低在程式碼當中的圈複雜度呢?

就以剛剛上面的這一個函式來講,推薦兩個平常大家在寫程式的時候可以去參考的切入的角度:

  1. 減少條件判斷分支: 假設判斷的分支會增加圈複雜度,那麼我們就透過一些方式來減少條件判斷的分支。

  2. 將分支抽到輔助函式: 假設沒有辦法有效地去減少分支,那麼我們可以把分支抽到輔助函數,也就是大家常聽見的 helper function 當中,然後讓單獨的某一個函式的複雜度降低,因為複雜度被分散到輔助函數當中了。

(備註:在課程影片中我們談了兩個具體的重構實例,逐字稿比較難表達,推薦這段務必看影片內容)

認知複雜度(Cognitive Complexity)

上面談了圈複雜度,不過相信大家平常在寫程式的時候可能會有一個感覺,假設我們純粹的用圈複雜度去衡量,有時候可能並不是真的這麼準確。

單純的使用圈複雜度,其實並沒有把很多人類的維護者在看程式碼的時候會有的一些比較認知角度上面的這種問題給考慮進來。當我們進步去考慮認知角度會有的問題的時候,很多時候即使圈複雜度相同的程式碼,像在右邊的這個函式就比較容易讓多數的人讀起來腦袋會打結、負擔會比較大。

所以這也是為什麼,其實現在業界比較少使用圈複雜度這個比較久以前提出來的衡量方式,更多的會是使用認知複雜度的衡量方式。認知複雜度會把人類在讀程式碼時會有的這些額外認知負擔加總進來。所以巢狀結構的認知複雜度會比較高一點。

好讀不等於好改

要讓程式碼的可維護性的複雜度降低,其實很多時候程式碼好讀不等於程式碼好改。

以這邊的範例來講,在下面的例子當中,大家看過去應該不會覺得這個程式碼不好讀。這邊我們有不同的元件都寫了某一個 #FF5050 這樣子的一個顏色。這段程式碼讀起來,相信多數人應該不會覺得是不好讀、有負擔的。

但是假設今天這三個不同的元件,是散落在程式碼庫的三個不同的地方,而假設在這個狀況下我們要去改顏色,相信多數人可能就會覺得這時候改起來會比較麻煩一點。

但假如我們可以把這個顏色抽出來,比如在下面這個版本,把它抽到一個叫 primary(主要顏色)的這個變數當中,這個時候未來假設在不同的頁面中,都要同時去修改 primary 主要顏色的色碼,我們就只用去改 primary 這個變數的值,就可以一次的完成。

即使用到這個顏色的元件有超過 100 個,我們也只需用改一個地方,就會非常容易改。但假設在第一個版本,即使不會不好讀,但假設有 100 多個不同的元件都要用這個顏色,我們又用該版本的方式寫,那麼要改的時候就要去改 100 多個地方,就會變得相對比較麻煩一點。

這邊希望透過這個例子讓大家感受,程式碼好讀不代表程式碼就好改。我們在接下來的單元也會更強調可以用一些不同的原則讓程式碼變得更好改。

總結

最後我們稍微回顧一下,今天假設你要寫出好維護的程式碼,讓程式碼的複雜度降低是一個有效的方式。

如果要從可維護性的角度衡量程式碼的複雜度,推薦有兩個可以看的指標:

  • 程式碼好讀:一個新接觸這個程式碼的人,要花多久的時間能夠看懂這個程式碼?
  • 程式碼要好改:當某個未來的維護者要改動這個程式碼的時候,會要花多少精力?

以上,讓大家稍微感受一下程式碼的複雜度。接下來,我們在後面的單元會陸續談,可以如何有效的降低程式碼的複雜度,讓整體的程式碼變得更好維護。

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們