Day 28 談到,AI 擅長生成程式碼細節,但做判斷、做取捨的工作仍然落在人身上。今天是系列倒數第二篇,想換個方式收尾:借用工程團隊在事故之後會做的「事後檢討(Post-Mortem)」,不是檢討誰犯了錯,而是回頭檢視這 30 天裡每一個設計決定背後的取捨。Google 的 SRE 團隊有個原則叫「無究責(Blameless)」:重點是找出系統哪裡有缺口,而不是追究個人,因為「你修不好人,但可以修好系統」。我想用同樣的態度,回頭看這個系列的設計。
電腦科學家 Fred Brooks 在經典論文〈No Silver Bullet〉裡,把軟體的複雜度分成兩種。**附帶複雜度(Accidental Complexity)**是工程師自己製造、也能靠更好的工具或方法解決的困難,例如早期的組合語言,被高階語言取代後大幅減少。**本質複雜度(Essential Complexity)**則是問題本身固有的複雜度:如果使用者真的需要 30 個功能,這些需求不會因為換了新框架就消失。
Brooks 的結論是:沒有任何單一技術,能讓軟體生產力在十年內提升一個數量級,因為大部分困難來自本質複雜度。這個論點放在 Day 28 的脈絡很有意思:AI 確實大幅降低了附帶複雜度(寫程式碼變快了),但「這個系統到底該做什麼、該怎麼取捨」仍然是本質複雜度,AI 並沒有讓它消失。
架構領域的書籍《Fundamentals of Software Architecture》把這件事寫成第一定律:軟體架構裡的一切都是取捨(Everything in software architecture is a trade-off)。沒有哪個設計選擇放諸四海皆好或皆壞,每個選擇都要放回系統的目標與限制裡評估。常見的例子包括:單體架構因為網路呼叫少而效能較好,微服務則比較容易擴展,這是效能與擴展性的取捨;關聯式資料庫結構單純,NoSQL 更有彈性但要接受最終一致性,這是簡單與彈性的取捨;多重驗證讓帳號更安全,卻增加登入的麻煩,這是安全與易用的取捨。
既然沒有標準答案,那「當初為什麼這樣選」就比「選了什麼」更值得留下紀錄。業界的做法是架構決策紀錄(Architectural Decision Record,簡稱 ADR):每一則紀錄只記一個重要的架構決策,寫下決策的理由、取捨與後果,累積起來就是專案的「決策日誌」,讓日後接手的人看得懂系統為什麼長這個樣子。
回頭看,這個系列其實反覆出現「沒有最佳解,只有取捨」的時刻:
byId 結構避免資料重複,查找也快,但某些畫面需要多一層組裝,是「一致性」與「查詢便利」的取捨。以 Day 7 的 SSOT 為例:訂單總金額要即時計算,還是存成一個固定欄位?即時計算能確保只有一份真相,但每次查詢都要重算;存固定欄位查詢快,卻要面對「商品單價改了,舊訂單要不要跟著變」的問題。把這個決定寫成 ADR,大概長這樣:
# ADR-001:訂單總金額要即時計算還是儲存?
## 背景
訂單列表每頁要顯示 50 筆訂單的總金額;商品單價可能被代購業者調整。
## 決策
訂單建立時,將「下單當下的總金額」存入 orders.total,不再隨商品單價變動。
## 理由與取捨
- 歷史訂單金額應該等於買家當時實際付的錢,所以這個值不屬於「衍生資料」。
- 代價:orders.total 與商品單價可能不一致,需在文件中明確標示它是快照。
## 後果
若未來要做「改價後補差額」功能,需要另外設計調整紀錄,不能直接改 total。
注意:這個決定看起來跟 Day 7 的「衍生資料不要另外儲存」好像矛盾,但關鍵在於「歷史訂單的金額」本來就是當時的事實,而不是從單價推導出來的結果。同一個原則,放在不同的業務前提下,會得到不同的結論,這正是取捨的意思。
事後檢討的價值,在於誠實面對限制。這個系列裡每個原則的結論,都有它成立的前提:Day 27 的索引、複製、分片、佇列,是業界常見的做法,但真正採用之前,仍需要拿實際的資料量與流量去驗證,不是看到「資料變 100 倍」就一股腦全部導入。反過來說,當我說某個原則「划算」時,也該問自己:它在什麼情境下不划算?例如一支只有三十行的小工具腳本,硬把它拆成表現層、邏輯層、資料層,就是過度設計。能說出一個原則的適用條件,也能說出它的失效條件,才算真正理解它。
前面提及No Silver Bullet 說的不是「所有技術都沒用」,而是提醒我們:軟體設計的大部分困難,來自問題本身,而不是工具不夠好。所以這 30 天學到的每個原則,與其說是該怎麼做的標準答案,不如說是在什麼條件下,這個取捨划算,跟經濟學一的核心一樣。所以真正該養成的習慣,是每做一個決定,就把理由與代價寫下來。