iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
Software Development

當 AI 寫得比你讀得快:Code Review 該審什麼系列 第 16 篇

Day 16:光讀程式碼找不出的東西——認知負荷與維護成本怎麼衡量

  • 分享至 

  • xImage
  •  

前言:「這段程式碼沒有 bug,review 過關,問題在哪?」

Day 11-13 拆解的三個案例(空的 UnitOfWork、假的重試、死碼 DTO),都有一個共同點:單獨看,它們都「沒有 bug」——執行起來不會拋例外,測試會通過,甚至邏輯順著讀下去也說得通。如果 review 的標準只有「有沒有明顯錯誤」,這三個案例全部會過關。

但這個系列走到這裡,想帶出一個更難量化、卻同樣重要的問題:一段程式碼「沒有 bug」,不代表它沒有成本。 它花掉的是另一種成本——認知負荷(cognitive load)跟維護成本,這兩者不會反映在測試報告或 bug 數量上,卻真實地決定了半年後這個系統改不改得動。

今日目標

  • 理解「沒有 bug」跟「沒有成本」是兩件不同的事
  • 認識認知負荷這個概念在程式碼閱讀情境下具體指什麼
  • 用版本 A、版本 B 的呼叫鏈長度作對照,具體感受認知負荷的差異
  • 理解「檔案數/改動幅度」這類間接指標,為什麼比「有沒有 bug」更適合衡量維護成本
  • 為 Day 17 之後「把規則寫成可執行檢查」的解法鋪陳動機

認知負荷:讀懂一段程式碼,需要同時記住多少東西

認知負荷指的是,理解一段程式碼在做什麼,需要讀者的大腦同時記住、追蹤多少個「移動的部分」。版本 A 的 publish() 呼叫鏈是:PublishArticleService::publish() → Article::publish() → ArticleRepository::save(),三步,讀者同時要記住的物件只有 Article 一個。

版本 B 的同一個操作,呼叫鏈是:PublishArticleService::publish() → runInTransaction() 包住 → CommandBus::dispatch() → PublishArticleCommandHandler::handle() → ArticleAggregate::publish() → EventDispatcher::dispatch()(觸發 ArticlePublishedEvent,被 LoggingEventListener 接住)→ ArticleRepository::save()(依序穿過 TransactionalArticleRepositoryDecorator → RetryingArticleRepositoryDecorator → CachingArticleRepositoryDecorator → LoggingArticleRepositoryDecorator → ArticleTableGateway)。要完整理解這一次 publish() 呼叫的行為,讀者必須同時在腦中維護至少 9-10 個物件的狀態跟彼此的呼叫關係。

這不是「難不難」的問題,是人類工作記憶本來就有限,超過負荷的部分不是被記住,而是被略過。 而被略過的部分,正是 SqliteUnitOfWork 沒有真的做交易保護、RetryingArticleRepositoryDecorator 永遠只試一次這類細節最容易被藏住的地方——不是因為它們刻意隱藏,而是因為讀者的注意力早就被前面七、八層呼叫消耗完了。

維護成本:改一個欄位要動幾個檔案,是比「有沒有 bug」更誠實的指標

「有沒有 bug」是一個二元判斷,而且只反映「現在」這個時間點的狀態。但工程實務裡真正累積起來的成本,是「未來每一次修改,要花多少力氣」——這正是 Day 13 提到的量化實驗想回答的問題:新增一個「分類」欄位,乾淨版本改 3 個檔案,過度設計版本(僅計有被呼叫的部分)要改 10-11 個檔案。

❌ 只用「有沒有 bug」衡量程式碼品質:

這個 PR 跑起來沒有錯誤、測試都過
→ 品質沒問題

✅ 同時用改動幅度衡量維護成本:

這個功能只有 4 條業務規則,
下一次新增一個欄位,預期要改幾個檔案?
如果答案是兩位數,問題已經存在,只是還沒發作。

版本 B 的 92% 死碼,更是這個問題的極端案例:這些程式碼此刻不會被執行、不會被測試碰到,所以「有沒有 bug」這個問題根本無從問起——但它們仍然佔用著每一次搜尋、每一次重構、每一次新人 onboarding 時必須先排除的認知空間。這是一種「沒有 bug、卻持續在收租」的成本,而且沒有人在為它記帳。

為什麼這個問題沒辦法只靠更仔細的 review 解決

Day 15 已經講過,92% 死碼這種規模問題,光憑肉眼幾乎不可能在短時間內確認。認知負荷跟維護成本又比死碼比例更難量化——它們不是「對或錯」的二元判斷,而是連續的、累積的效果,今天看起來還好,半年後疊加起來才會顯現。這正是這個系列要在第三部轉向「把期待寫成可執行規則」的原因:認知負荷跟維護成本雖然難以精確衡量,但它們的一部分代理指標——呼叫鏈深度、改動檔案數、死碼比例——是可以被量化、被自動檢查的。 這呼應這個系列反覆強調的主題句:AI 沒有發明過度設計,它只是讓過度設計的速度追上了你按下 Enter 的速度;Review 要跟得上,審的就不能再是程式碼本身,而是產生程式碼的規則。 這裡的「規則」,正是把認知負荷、維護成本這類抽象但真實的成本,轉換成具體可以被自動驗證的數字門檻。

今日思考題

如果請你為你目前的專案定義一個「維護成本」的代理指標(例如平均改動一個欄位要動幾個檔案、平均呼叫鏈深度),你會選哪一個指標?這個指標現在有沒有被追蹤?

今日重點回顧

  • 「沒有 bug」是二元、當下的判斷,不代表這段程式碼沒有累積性的成本
  • 認知負荷指讀懂一段程式碼需要同時追蹤的「移動部分」數量:版本 A 的 publish() 只需追蹤 1 個物件,版本 B 需要追蹤 9-10 個
  • 改動幅度(新增一個欄位要動幾個檔案)是比「有沒有 bug」更能反映長期維護成本的指標
  • 認知負荷跟維護成本雖難精確衡量,但可以透過呼叫鏈深度、改動檔案數、死碼比例等代理指標量化並自動檢查

明日預告

第二部的案例拆解到這裡告一段落。Day 17 開始進入第三部解法篇,回到 Outside-In TDD/ATDD 這套十幾年前就存在的紀律,講清楚它本身就是最古老的過度設計解藥。

老派工程師的心得

寫這篇的過程中,我一直想找一個乾淨俐落的公式來定義「認知負荷」,但寫著寫著發現自己在硬湊。認知負荷這種東西,本質上就是模糊的、因人而異的——資深工程師讀版本 B 的呼叫鏈可能比新人快一些,但快多少、快在哪裡,很難給出一個放諸四海皆準的數字。這反而讓我更確定一件事:正因為這類成本天生模糊,才更需要找出可以被量化的代理指標,而不是繼續依賴「讓資深的人多看幾眼」這種同樣模糊的解法。這個結論,某種程度上也是我自己想清楚之後才寫出來的,不是動筆前就有的答案。


上一篇
Day 15:逐行 review 20000 行要花多久——一個真實的討論
系列文
當 AI 寫得比你讀得快:Code Review 該審什麼 共 16 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言