写出好维护的代码 — 什麼是软体複雜度? 如何降低?

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 上追蹤我們