面對一個規模較大的 PR,很多團隊的直覺解法是「那就多花點時間、找資深的人仔細看」。這句話背後的假設是:review 的成本跟程式碼行數是線性關係,只要願意投入時間,終究審得完。
今天要誠實地拆解這個假設,用版本 B 的 21,727 行當例子。先講清楚我不會做的事:我不會給你一個「逐行 review 花了幾分幾秒」這種精確到看起來很科學的數字——那種數字不管是我自己編的還是隨口估的,都經不起檢驗,反而會削弱這篇文章的可信度。我要講的是一個更誠實、也更重要的結論:92% 的程式碼是死碼這件事,光憑肉眼在短時間內幾乎不可能確認。
逐行閱讀程式碼,擅長回答的問題是:「這個 if 判斷式在做什麼」「這個方法的參數型別對不對」「這段邏輯有沒有明顯的 bug」。這些問題的答案,通常只需要看這一個檔案、甚至這一個方法就能判斷。
但版本 B 真正的問題不是藏在某一行邏輯裡的 bug,而是規模層級的問題:683 個檔案、約 19,989 行,合計佔全部程式碼 92% 的部分,是從未被任何測試或執行路徑碰過的死碼。要確認「這個 ArticleArchiveedEvent 有沒有被任何地方呼叫」,不是讀這個檔案本身能回答的問題——它需要你追蹤這個類別在整個 745 個檔案裡有沒有出現在任何 new 或依賴注入的組裝點,而且要對每一個看起來可疑的類別都重複這個動作。這是一種需要「看過所有檔案的呼叫關係」才能回答的問題,而不是「看懂某一個檔案在做什麼」的問題,兩者需要的認知動作完全不同。
版本 B 的 745 個檔案分散在超過 20 個子目錄裡(Domain/Aggregates、Domain/Events、Domain/Listeners、Domain/Specifications、Application/Commands、Application/CommandHandlers、Application/Queries、Application/DTOs、Application/Mappers、Infrastructure/Repositories/Decorators……)。光是建立「這個系統的資料夾結構長什麼樣、每個目錄大概負責什麼」這張心智圖,就已經是一件不算輕鬆的事,而這還只是第一步——確認死碼還需要在這張地圖上,對每一個類別逐一做「有沒有被呼叫」的交叉比對。
我自己在準備這個系列、實際去讀這個過度設計版本原始碼的過程裡,光是定位「SqliteUnitOfWork 沒有真的做交易保護」這件事,就不是一眼看過去就能確定的——要先找到這個類別、再確認它實作的介面、再往回追蹤呼叫它的地方(PublishArticleService::runInTransaction()),最後才能拿它的方法本體跟 PDO 的交易 API 對照,判斷這裡到底有沒有交易保護。這是一個具體案例、只涉及一個類別,尚且需要好幾個步驟的追蹤;把同樣的追蹤動作重複做在 683 個死碼檔案上,是完全不同量級的工作。
❌ 對逐行 review 的過度樂觀:
「這個 PR 有 500 個檔案,
安排兩個資深工程師仔細看兩天應該夠」
✅ 更誠實的判斷框架:
「單一檔案的邏輯對不對」→ 適合肉眼 review
「這個類別/方法有沒有真的被呼叫到」→ 適合工具(靜態分析、覆蓋率、死碼偵測)
「改動範圍跟需求範圍成不成比例」→ 適合量化規則(檔案數/行數門檻)
如果一個問題的規模讓「肉眼在合理時間內確認」變得不切實際,那麼把責任壓在「找更資深的人仔細看」上,只是在假裝問題有被解決。 這正是這個系列的主題句在講的事:AI 沒有發明過度設計,它只是讓過度設計的速度追上了你按下 Enter 的速度;Review 要跟得上,審的就不能再是程式碼本身,而是產生程式碼的規則。死碼比例、改動範圍比例這類問題,天生就該交給可執行的規則跟工具去持續驗證,而不是每次都重新指派一個人去肉眼確認一遍——這也是第三部(Day 17 之後)要具體講的內容。
在你的專案裡,有沒有類似「死碼比例」這種需要看過整個程式庫的呼叫關係才能回答的問題?目前是靠人工複查,還是已經有工具在持續檢查?
Day 16 會討論一個更難量化、但同樣重要的東西:光讀程式碼找不出來的認知負荷跟維護成本,該怎麼衡量。
我一開始其實有點想在這篇放一個具體的「花了 N 分鐘」的數字,因為那種數字讀起來很有說服力。但仔細想想,那個數字會因為我對這個系統熟悉的程度(我自己就是準備素材的人)而完全失真,放進文章反而是一種不誠實的包裝。這次選擇不編造數字,某種程度上也是在示範這個系列想傳達的態度:面對複雜度問題,誠實地承認「這件事很難量化、很難靠人力驗證」,比硬湊一個聽起來很精確的數字更有價值。