前言
這一兩年 AI 寫 code 越來越強,業界開始有人說,軟體工程那些老東西是不是可以丟了。最誇張的是一派叫「specs to code」:工程師只要寫好規格,剩下交給 AI 產 code;程式壞了也不用看 code,改規格、再生成一次就好,把 AI 當成編譯器。
聽起來很美好,但真的這樣玩過就會發現,一直重新生成,最後只會得到一坨沒人能維護的 code XD
我之前常常把客戶的需求整段貼給 Claude Code,做出來不對,第一個反應也不是去看 code,而是補一句需求,再叫它重做一次。重做個三、四輪,功能好像都有了,但同一件事有兩三種寫法散在不同檔案,最後要改一個小地方,連 AI 自己都改錯 XD
Matt Pocock 在 AI Engineer 有一場演講〈Software Fundamentals Matter More Than Ever〉,把 AI 常見的翻車方式整理成五種,每一種都能在一本放了很多年的經典裡找到解法。
便宜的是 code,貴的是爛 code
Specs to code 背後的假設是「code 很便宜」,反正 AI 一秒產一大堆,壞了就丟。
但《The Pragmatic Programmer》很早就講過軟體熵(software entropy):事情放著只會越來越亂。每次只顧眼前那一個改動、不管整體設計,codebase 就會一路爛下去,而且回不去。
John Ousterhout 在《A Philosophy of Software Design》裡對複雜度的定義也很直白:任何讓系統難以理解、難以修改的結構都算。一個改了就會出 bug 的 codebase,就是爛 codebase。
AI 只有在好改的 codebase 裡,生產力才會很驚人。所以軟體工程的基本功,現在比以前任何時候都重要。
AI 翻車的五種姿勢
| AI 的症狀 | 人要補上的基本功 |
|---|---|
| 做出來的東西跟你腦中想的完全兩回事 | 先建立共同的設計概念,開工前讓 AI 把你問到底 |
| 回答又臭又長,講的詞你都聽不太懂 | 定義一份共同語言(ubiquitous language),讓雙方講同一套詞 |
| Code 看起來對,一跑就壞 | 用 TDD 把步伐縮小,回饋一次只接一點點 |
| AI 在 codebase 裡迷路,搞不懂現有邏輯 | 把一堆淺模組收成深模組,介面簡單、複雜藏在裡面 |
| 產出翻倍,人卻累到不行 | 介面自己設計,實作當灰箱丟給 AI |
左邊這些症狀,用過 agent 的人應該都遇過。右邊沒有一個是新東西。
先吵一架,再開工
AI 做出來跟你想的不一樣,通常是你們腦中的東西沒對齊。Fred Brooks 在《The Design of Design》裡講過「設計概念」(design concept):幾個人一起設計系統時,大家腦中共享的那個東西,很難整份寫進一份 Markdown 裡。你跟 AI 一起設計也一樣。
AI 的毛病是太急,你話還沒講完它就開始產 plan。比較好的做法是反過來,叫它先別動手,一直追問你,把設計樹的每個分支、每個決定都問清楚,問個四十到一百題都很正常。等雙方真的有共識了,再把結論寫成 PRD 或 issue。
另一個問題是雞同鴨講。AI 解釋自己在做什麼的時候,常常用一堆你們專案裡根本沒人在用的詞,就像資深工程師想跟完全不懂技術的領域專家解釋晶片架構一樣。這正是 DDD 想解決的問題,做法也很 DDD:把專案裡的核心名詞和定義整理成一張表,放進一個 Markdown 檔,每次規劃都叫 AI 先讀。詞對齊了,它的 thinking 會短很多,做出來的東西也比較貼近原本的 plan。
這個部落格從 Sanity 搬到 Markdown 的時候,要決定的事多到嚇人:主網域用哪一個、網址要不要帶語言、中英文用不用同一個 slug、tag 要幾篇以上才值得開一頁。這些如果沒先問清楚,AI 會自己挑一個看起來合理的答案寫下去,等你發現的時候,網址早就被 Google 收錄了。
術語也是。PizzaNote 有 topic(前端、AI、生活)也有 category(像 SEO),category 還掛在 topic 底下,但文章 frontmatter 裡寫的 category 其實放的是 topic。這種專案裡的歷史包袱,沒寫成一張表,AI 每次都要重新猜一次。
推薦 Matt Pocock 開源的 skills,可以直接裝進 Claude Code。grill-me 會一直追問到沒有它自己偷偷假設的地方;ubiquitous language 會掃 codebase,幫你產出術語表;improve codebase architecture 會找出可以收成深模組的地方。
車速不能超過車燈
就算你給了 AI TypeScript 型別檢查、讓它開瀏覽器、還準備好自動化測試,它寫出來的 code 還是常常跑不起來。
《The Pragmatic Programmer》有個比喻很傳神:outrunning your headlights,車開得比車燈照得到的地方還快。回饋的頻率,就是你開發速度的上限。AI 的習慣是一口氣噴一大堆 code,最後才想到要跑測試,這樣不翻車才奇怪。
TDD 就是幫 AI 裝煞車。先寫一個小測試、讓它通過、再重構順便想一下設計,一次只走一小步。
我自己也被車燈照不到的地方撞過。PizzaNote 改版的時候,AI 幫我把標題換成整套中文 webfont,畫面很漂亮,在電腦上看也沒問題,結果手機的 LCP 直接飆到 12 秒。後來改成 build 的時候只抓文章用到的字做子集,還在 prebuild 加了一個 check-content,標題太長、描述長度不對就跳警告。現在 AI 寫完文章,build 一跑就知道有沒有超標,不用等上線才發現。
介面你顧,實作它寫
AI 很擅長生出一堆淺模組(shallow module):每個模組功能少少的,介面卻很複雜,東一塊西一塊。AI 自己在這種架構裡探索,也一樣會迷路,搞不懂誰依賴誰。
Ousterhout 給的解法是深模組(deep module):把一大包複雜的功能藏在一個很簡單的介面後面。AI 不用懂深模組裡的每一行,只要透過乾淨的介面跟它互動、在邊界上寫測試就好。把相關的 code 找出來收進深模組,系統就會變得很好做 TDD,AI 也比較不會迷路。好測的 codebase,一定也好維護。
第五個問題也是從這裡解:產出翻倍,人卻累到不行。會累是因為你想把每個實作細節都塞進腦袋,跟 AI 一起同步,等於在 micro-manage 它。模組化本來就是為了降低認知負擔,所以把深模組當成灰箱(gray box)就好,介面和測試邊界你自己設計,裡面的實作交給 AI。金流這種真的不能錯的核心邏輯除外。
寫 PRD 的時候也一樣,重點寫模組要怎麼變、介面長怎樣,不要寫一堆實作細節。這也是 Kent Beck 說的,每天投資一點在系統設計上。
PizzaNote 裡就有一個這樣的例子。每一頁的 SEO 設定都只呼叫一個 pageMetadata,傳標題、描述、網址和分享圖進去就好;canonical、hreflang、x-default、OG、Twitter card 這些細節全部藏在裡面。我在意的是這個介面好不好用、輸出的標籤對不對,裡面怎麼組,交給 AI 寫就好。要加新頁面的時候,AI 也只要照著呼叫,不用每頁重寫一次 SEO。
給剛入行的你
如果你是非本科、或剛開始寫程式,上面那些深模組、DDD 可能有點遠。日常用 AI 的時候,我覺得記住三件事就夠了。
第一,把 AI 當成一個很聰明、但剛報到的工讀生。跟 AI 溝通其實跟跟人溝通一樣,交辦事情的時候問問自己:指令夠不夠清楚?他知道怎麼做才算你要的嗎?很多時候不能只丟一個模糊的目標過去,要告訴他怎麼做、給他一份 step-by-step 的 plan,開工前先讓他把所有疑問都丟出來,就跟前面的 grill-me 一樣。
第二,自己要有領域知識,不要當傳聲筒。真實的軟體公司裡,不會讓客戶直接去指揮工程師,也沒有工程師會看著 Google Play 的一星評論通靈修 bug。中間一定有懂 domain 的 PM 或資深同事,把問題翻成清楚的需求。跟 AI 合作的時候,那個人就是你。有了領域知識,你才會從單純下指令的使用者,變成跟 AI 協作的人;從回報 bug 的測試員,變成設計系統的人。
第三,把回饋迴圈建起來。寫程式一定會有 bug。除錯的流程其實很固定:蒐集錯誤資訊、找出原因、用對的方式修、再測一次。讓 AI 直接讀得到 terminal 或錯誤平台的訊息,再叫它寫出正確的測試來驗證,除錯時間真的會少很多。
AI 衝前線,你畫地圖
Specs to code 最致命的地方,是讓我們不再投資系統設計。
Matt 的比喻是,AI 就像前線的士官(sergeant on the ground),大量改動、繁瑣實作都可以丟給它。但上面總要有一個人看整張地圖、決定往哪打,那個人是你。要做好這件事,靠的就是軟體工程界磨了二十幾年的基本功。
你還是可以讓 AI 一次幫你產幾百行 code,享受 vibe coding 的快感。但是,下次按「重新生成」之前,先把書架上那本《The Pragmatic Programmer》拿下來翻一下,好嗎?
參考資料
- Matt Pocock,〈Software Fundamentals Matter More Than Ever〉,AI Engineer
- Matt Pocock,mattpocock/skills(GitHub)
- David Thomas、Andrew Hunt,《The Pragmatic Programmer》
- John Ousterhout,《A Philosophy of Software Design》
- Frederick P. Brooks,《The Design of Design》
- Eric Evans,《Domain-Driven Design》