iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
Software Development

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

Day 10:版本 B——同一組測試,AI 自由發揮寫出來的過度設計版本

  • 分享至 

  • xImage
  •  

前言:「多包幾層架構,應該只是比較『穩健』吧?」

Day 9 看完版本 A 的 3 個檔案之後,你可能會想:好,這是刻意做的極簡示範,實務上系統當然會需要多一點架構,CQRS、Repository 介面、事件機制,這些不是業界公認的最佳實踐嗎?

這句話本身沒有錯——CQRS(Command Query Responsibility Segregation,將寫入與查詢的職責分開處理)、Repository Pattern、Decorator Pattern 都是有明確使用情境的成熟設計模式。**問題不在於這些模式本身,而在於「有沒有真正的情境需要它們」。**今天要具體攤開版本 B 的樣貌,讓你看到同一組 10 條驗收測試、一模一樣的 4 條業務規則,可以在「自由發揮」的情況下長成什麼樣子。

今日目標

  • 掌握版本 B 的整體規模數字:745 個檔案、21,727 行
  • 理解「真正被呼叫的核心程式碼」跟「從未被執行路徑碰過的死碼」的比例
  • 看到 CQRS、四層 Repository 裝飾器、事件系統在這個版本裡實際的組裝方式
  • 認識「每一段程式碼單獨看都合理」跟「整體合理」是兩回事
  • 建立 Day 11-13 逐一拆解案例前需要的整體地圖

硬數字:業務規則沒變,程式碼量變了 87 倍

指標 版本 A(乾淨版) 版本 B(過度設計版) 倍數
檔案數 3 745 248 倍
總行數 249 21,727 87 倍
驗收測試數 10 10(一字未改) 1 倍
業務規則數量 4 條 4 條(完全相同) 1 倍

而在這 745 個檔案裡,真正會被 PublishArticleService/ArticleRepository 這兩個入口點呼叫到的類別,只有 62 個檔案、1,738 行(Domain Aggregate、4 條 Specification、CQRS 的 Command/Query/Handler/Bus、4 層 Repository 裝飾器、EventDispatcher 等)。

其餘 683 個檔案、約 19,989 行,佔全部程式碼的 92%,是「以防萬一」預先建好、從未被任何測試或執行路徑碰過的死碼:50 種假設性未來功能(封存、還原、精選、標籤、排程發佈、複製、匯出匯入、翻譯、留言、按讚、檢舉、置頂、鎖定、訂閱……),每種都配了完整的 Command / Handler / Validator / Exception / Event / Listener 六件套與一份批次版本;25 個沒有任何業務規則用到的欄位 Value Object;5 種多餘的 Cache/Logger 後端;5 種沒被組進真正裝飾器鏈的 Repository 裝飾器變體。

真正被組裝進去的樣子:入口點長怎樣

ArticleRepository 這個版本 B 的對外入口,建構子裡組出一條四層裝飾器鏈:

public function __construct(private PDO $pdo)
{
    $logger = new ArrayLogger();

    $gateway = new ArticleTableGateway($pdo, new ArticleSqlBuilder(), new ArticleRowToAggregateMapper());
    $logging = new LoggingArticleRepositoryDecorator($gateway, $logger);
    $caching = new CachingArticleRepositoryDecorator($logging, new ArrayCache());
    $retrying = new RetryingArticleRepositoryDecorator($caching, $logger);
    $this->decorated = new TransactionalArticleRepositoryDecorator($retrying, $logger);
}

版本 B 原始程式碼裡對這個類別本身留了這段註解,直接點出了問題:

每一層都對應一個「聽起來很專業」的非功能需求(記錄、效能、可靠性、一致性),但對這個示範專案的規模而言,四層裝飾器沒有一層是真正必要的——這正是本系列文章想具體示範的過度設計。

PublishArticleService 則把每個操作包成 Command,透過 CommandBus 分派給對應 Handler,並用一個 UnitOfWork 包住整個流程:

public function publish(int $id): void
{
    $this->runInTransaction(fn () => $this->commandBus->dispatch(new PublishArticleCommand($id)));
}

呼叫鏈變成:PublishArticleService → CommandBus → PublishArticleCommandHandler → ArticleAggregate → ArticleRepository(四層裝飾器)→ ArticleTableGateway。版本 A 的同一個操作是兩層,版本 B 是七層以上。

值得注意的是,建構子裡還留著這樣一行:

// FindArticleQueryHandler 建構後沒有被指派給任何屬性、也沒有被
// 呼叫——保留在這裡純粹是「查詢單一資源」被視為標準操作、
// 理應要有對應 Handler 的慣性產物,即使這個服務目前完全靠
// ArticleRepository::find() 直接處理單筆查詢。
new FindArticleQueryHandler($repository);

❌ 「架構完整」的直覺判斷:

CQRS + Repository + Decorator + Event-Driven
= 看起來像一個「正式的企業級系統」該有的樣子

✅ 該問的問題:

這 4 條業務規則,
真的需要「寫入/查詢分離」「四層裝飾器」「事件廣播」
才能實現嗎?

每一段程式碼單獨看都能編譯、都符合某種教科書式的最佳實踐,但合在一起,就是一個要花掉大量時間才能搞懂「這裡到底在幹嘛」的迷宮。 這正是這個系列的主題句想講的:AI 沒有發明過度設計,它只是讓過度設計的速度追上了你按下 Enter 的速度;Review 要跟得上,審的就不能再是程式碼本身,而是產生程式碼的規則。

今日思考題

如果你打開一個新專案,看到裡面已經有 CQRS、Repository 裝飾器鏈、事件系統,你會直覺認為這是「架構良好」還是先問「這些機制實際被哪裡呼叫到」?你目前的 review 習慣,會不會被「看起來很專業」的結構本身說服?

今日重點回顧

  • 版本 B 共 745 個檔案、21,727 行,其中只有 62 個檔案、1,738 行真正被入口點呼叫到
  • 92% 的程式碼(約 683 個檔案)是從未被任何測試碰過的死碼
  • ArticleRepository 組裝了四層裝飾器鏈,PublishArticleService 用 CQRS + UnitOfWork 包住每個操作
  • 業務規則沒有增加,呼叫鏈卻從兩層變成七層以上

明日預告

Day 11 開始逐一拆解案例,先從 Repository 裝飾器鏈跟 UnitOfWork 下手——看看那些「聽起來很專業」的非功能需求,實際上有沒有真的在保護什麼。

老派工程師的心得

看著版本 B 的檔案樹一層一層展開時,我心裡其實浮現一種熟悉感——這跟我過去看過某些「資深工程師主導」的專案架構,長得驚人地像。差別只在於,人類要花好幾個禮拜的技術決策會議跟重構,才能長出這樣一棵樹;AI 一次對話就能生出來。這不是說 AI 特別會過度設計,而是它把人類工程師原本就有的這個傾向,用產出速度放大到我們來不及反應的程度。


上一篇
Day 9:版本 A——用 Outside-In TDD/ATDD 寫出來的乾淨版本長怎樣
下一篇
Day 11:案例拆解——多出來的 Repository 裝飾器鏈跟 UnitOfWork 在保護什麼
系列文
當 AI 寫得比你讀得快:Code Review 該審什麼 共 13 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言