Day 9 看完版本 A 的 3 個檔案之後,你可能會想:好,這是刻意做的極簡示範,實務上系統當然會需要多一點架構,CQRS、Repository 介面、事件機制,這些不是業界公認的最佳實踐嗎?
這句話本身沒有錯——CQRS(Command Query Responsibility Segregation,將寫入與查詢的職責分開處理)、Repository Pattern、Decorator Pattern 都是有明確使用情境的成熟設計模式。**問題不在於這些模式本身,而在於「有沒有真正的情境需要它們」。**今天要具體攤開版本 B 的樣貌,讓你看到同一組 10 條驗收測試、一模一樣的 4 條業務規則,可以在「自由發揮」的情況下長成什麼樣子。
| 指標 | 版本 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 習慣,會不會被「看起來很專業」的結構本身說服?
ArticleRepository 組裝了四層裝飾器鏈,PublishArticleService 用 CQRS + UnitOfWork 包住每個操作Day 11 開始逐一拆解案例,先從 Repository 裝飾器鏈跟 UnitOfWork 下手——看看那些「聽起來很專業」的非功能需求,實際上有沒有真的在保護什麼。
看著版本 B 的檔案樹一層一層展開時,我心裡其實浮現一種熟悉感——這跟我過去看過某些「資深工程師主導」的專案架構,長得驚人地像。差別只在於,人類要花好幾個禮拜的技術決策會議跟重構,才能長出這樣一棵樹;AI 一次對話就能生出來。這不是說 AI 特別會過度設計,而是它把人類工程師原本就有的這個傾向,用產出速度放大到我們來不及反應的程度。