AI 開發的第一性原理:停止 CI,放棄 Code Review,擁抱 AI Pair Programming

過去幾個月,我帶團隊密集迭代了一個 AI 開發工具。踩了很多坑,也驗證了一些反直覺的發現。核心結論:大模型的開發效率、開發能力、開發質量已經徹底驗證過了,絕無問題。但要發揮最大價值,需要掌握正確的協作方法——有些和傳統工程實踐一致,有些則完全相反。
我不打算給出一套完美方法論。AI 能力還在快速進化,今天的最佳實踐可能明天就過時。以下更像是一份實戰筆記:哪些做法有效、哪些失敗、背後的原因是什麼。這些發現中,最讓我意外的是對傳統工程實踐的衝擊——我們逐漸停止了 CI 流程,放棄了人工 Code Review,轉而全面擁抱 AI Pair Programming。這不是偷懶,而是在實踐中發現了更有效的方式。聽起來像異端邪說,但背後有堅實的邏輯。
為什麼全面擁抱 AI Pair Programming
最初我有個天真的想法:既然 AI 這麼強,為什麼不讓它全自動跑一整晚,第二天早上收穫成果?人去睡覺,機器去工作,各取所長。我試了,結果是一次深刻的教訓。
那晚我設定好任務讓 AI 自動工作,安心去睡。第二天早上所有 unit test 都通過了,一度非常興奮,覺得發現了生產力的聖盃。然後花了一整天人工驗證,發現結果完全不是我要的。不僅如此,之前已經正確的東西被它改錯了,整個程式碼庫處於一種「看起來正常,實際一團糟」的狀態。因為所有測試都是綠的,問題被完美隱藏,我差點就把這些程式碼合進主分支。
問題出在哪?反覆檢討後找到了根本原因:漂移。AI 每一步都可能偏離你的 intent 一點點。單獨看每一步,偏差很小,甚至合理,挑不出明顯毛病。但沒人及時拉回來,偏差不斷累積——就像沒校準的指南針,每步只偏一度,走一百步後你已經完全迷失方向。幾小時的自動工作後,AI 可能已經嚴重偏離原始目標,而它自己完全意識不到。
更麻煩的是,AI 會「自我解釋」,在錯誤方向上越走越遠、越走越自洽。它不會停下來質疑自己是否走偏了——從它的視角看,每一步推理都合乎邏輯。它會為偏離找到完美的理由,甚至修改測試來適應錯誤的實現,最終呈現給你一個「自圓其說」的結果。這種自我合理化能力,在正確方向上是優勢,在錯誤方向上則是災難。
想到一個類比:AI 就像一個能力極強但缺乏整體視野的執行者,類似於技術能力頂尖但對業務目標理解有限的工程師。它能高品質完成當前這一步,程式碼漂亮、邏輯清晰,但不一定知道這一步是否還在通往正確目標的路上。它需要一個有整體視野的人來不斷校準方向——這就是 Pair Programming 的價值。
後來我徹底改變了協作方式,從「全自動」轉向「小步迭代」的 Pair Programming 模式。每 5-10 分鐘檢查一次 AI 的產出,發現偏差立刻拉回來,不讓它在錯誤方向上繼續積累。每個階段性成果確認後再進入下一步。看起來效率更低——人要花更多時間盯著——但實際效果是效率大幅提升,因為不用花大量時間事後校驗和返工。事後返工的成本是事中糾偏的十倍甚至百倍。
什麼時候可以放心讓 AI 獨立工作?確定性高的任務:介面已經定義清楚、只需實現的模組,不涉及設計決策;邊界清晰、不涉及其他模組改動的工作,沒有灰色地帶;重複性操作,比如批次重構、加快取、換皮——這些任務的「正確」是客觀可驗證的。團隊成員驗證過這個判斷:「介面已經是確定性的,我只需要實現其中一個模組,幾乎沒給 AI 回饋,它就做出了第一個版本。」另一個成員說:「加二級快取這個任務非常確定,AI 一次就搞定了,什麼都沒調,直接一個 PR。」這些案例的共同點:任務邊界和驗收標準都是清晰的,沒有需要人來判斷的模糊地帶。
反過來,探索期專案、涉及設計決策的工作、跨模組改動,都不適合讓 AI 獨立工作。這些場景的共同特點:什麼是「正確」本身就是模糊的,需要人的判斷。這時必須 Pair Programming,人和 AI 緊密配合——人負責方向和判斷,AI 負責執行和實現。這種分工讓雙方都發揮最大價值。
Intent 悖論:為什麼動手比思考更重要
這是我遇到的最反直覺的發現,直接挑戰「先想清楚再動手」的傳統智慧:沒有真正動手做,就不可能產生正確的 Intent。
按傳統軟體工程方法論,應該先做需求分析、寫設計文件、想清楚每個細節,然後再動手實現。這種方法的假設是:思考可以替代實踐,透過足夠深入的分析,可以在動手之前把所有問題想清楚。但在 AI 協作開發中,這個假設經常是錯的。你以為很清楚的需求,其實有大量靈活性和不確定性。很多關鍵的技術選型問題只有做了才會暴露,純靠「想」只能收集到表象,真正的複雜性藏在細節裡。
有個具體案例可以說明。團隊討論用 Tauri 還是 Electron 做桌面客戶端,看似簡單的技術選型問題。我們分析了一通——benchmark、bundle size、記憶體佔用、社群活躍度——最後得出結論 Tauri 更適合:更小的包體積、更好的效能、更現代的技術棧。這個分析過程看起來很嚴謹,每個論據都有資料支撐,我們對這個決定很有信心。
然後用 Tauri 做完第一個版本,code review 時才發現兩者的架構理念完全不同,而這個差異比 benchmark 資料重要得多。Tauri 採用 Sidecar 架構:應用的核心邏輯被編譯成獨立的 sidecar 處理程序,與 Tauri 主介面透過處理程序間通訊(IPC)協作。前端用系統原生 WebView 渲染,後端邏輯跑在 Rust 處理程序裡,兩邊是隔離的。這種架構對「前後端如何分工」有很強的約束——你必須想清楚哪些邏輯放前端、哪些放後端,跨邊界呼叫都要走 IPC。Electron 則完全不同,它把 Chromium 和 Node.js 打包在一起,前端和後端跑在同一個處理程序空間,通訊幾乎沒有成本,邊界可以很模糊,適合快速迭代、介面邏輯複雜的場景。
這個架構層面的差異,動手之前根本不知道——它不會出現在任何 benchmark 對比文章裡。討論時看的都是表面指標,真正的架構差異只有寫了程式碼、遇到具體問題才會暴露。我們在實現過程中發現 Tauri 的模式和需求有根本性衝突,而這個衝突在設計階段是看不到的。「紙上得來終覺淺,絕知此事要躬行」,AI 時代依然適用。
這就是 Intent 的悖論:你需要 Intent 來指導實現,但正確的 Intent 只能通過實現來獲得。雞生蛋、蛋生雞的問題,沒有完美解法,只有務實解法。解決方案:先做原型,通過實踐明確 intent,邊做邊完善,不要期望一次性完美。接受 intent 會迭代的事實,把第一次實現當作學習過程而不是最終產出。
這引出另一個反直覺發現:做完原型後重新實現,效率會極大提升,快到讓人難以置信。
還是 Tauri vs Electron 的例子。用 Tauri 花了大約 1 小時做出第一個版本,包括踩坑、除錯、學習各種概念。這個過程中充分理解了需求的複雜性,明確了真正的技術要求。然後我決定推翻重來,用 Electron 重做。讓 AI 開始工作,5 分鐘後它說任務完成,所有 unit test 全部 pass。我以為它搞錯了或者偷懶了,手動測了一下,所有功能都對,程式碼品質也很高。
為什麼第二遍這麼快?背後有清晰邏輯。所有坑都踩過一遍了——哪些地方有陷阱、哪些 API 有坑、哪些邊界條件需要處理,第一遍實現中都暴露了。Intent 極度清晰,不再有模糊地帶,每個功能的確切行為都在腦子裡。技術選型明確,不需要再做探索性工作。所有細節都被 capture 在上下文中,AI 第二遍實現時沒有任何需要猜測或推斷的地方。這四個因素疊加,第二遍變成了純粹的「翻譯」工作:把清晰的 intent 翻譯成程式碼。AI 最擅長的就是這種確定性翻譯。
這讓我重新理解了原型的價值——原型的價值不是產出程式碼,而是產出 learning。第一遍實現是為了學習:學習需求的真實複雜性,學習技術方案的實際限制,學習各種邊界條件和異常情況。第二遍才是為了產出,這時一切都清晰了,執行效率高得驚人。如果把第一遍當成最終產出,就會糾結於沉沒成本,捨不得推翻重來,最終抱著半吊子方案勉強推進。正確心態:原型就是用來扔的,不要對它產生感情。
TDD 成為必需品:兩階段任務法
「AI 最適合 TDD,AI 嚴重需要 TDD。」這是我在實踐中最確信的結論之一,雖然聽起來有點諷刺——傳統觀點認為 TDD 是給人設計的開發方法。
傳統軟體工程一直鼓吹 TDD,每本教科書都說先寫測試再寫程式碼是最佳實踐。但說實話,真正嚴格執行的專案不多,至少我見過的大部分專案都做不到。時間緊、任務重,寫測試的時間往往被壓縮,成為第一個被砍掉的環節。先寫程式碼、後補測試,甚至不補測試,是很多專案的常態。TDD 在人類時代,某種程度上是一個美好的理想——大家都知道應該這麼做,但現實約束讓它難以落地。
AI 時代徹底不一樣了。TDD 從「理想」變成了「必需品」。
AI 不喜歡寫測試,這是我觀察到的一個有趣現象。讓它同時寫程式碼和測試,它會本能地把精力放在程式碼上,對測試敷衍了事——偷懶、skip、寫一些不痛不癢的測試。測試覆蓋率看起來還行,實際驗證能力很弱。這不是 bug,而是某種「偏好」,它更願意展示寫程式碼的能力而不是寫測試的能力。先寫程式碼後讓它補測試,效果更差——它會寫出「讓現有程式碼通過」的測試。這些測試沒有驗證價值,因為它是根據實現來設計測試,而不是根據需求。測試成了程式碼的註腳,而不是守護者。
但如果先寫測試後寫程式碼,效果發生質的變化,正確率大幅提高。
我摸索出一個「兩階段任務法」。第一階段只讓 AI 寫測試,這是一個獨立任務。給它需求描述,要求寫 unit test,必須覆蓋 happy path、unhappy path、至少 N 個嚴重異常場景(網路故障、資料庫不可用之類)、至少 N 個安全問題場景(注入攻擊、越權訪問之類)。關鍵是:AI 不知道它接下來要寫實現。它會認真思考測試場景,因為沒有「實現細節」的包袱,不會被具體實現思路限制,可以更純粹地從需求角度思考「什麼情況需要測試」。
第二階段,把測試交給 AI(通常是新的 session),讓它根據需求描述和這些測試來實現功能。這時它有了明確驗收標準,可以用測試做自我檢查。每寫一部分程式碼就跑測試驗證,形成緊密反饋迴圈。測試成了它的「導航系統」,告訴它是否還在正確軌道上。
為什麼兩階段要分開、用不同 session?避免上下文汙染。如果在同一個 session 裡讓 AI 先寫測試再寫實現,它寫測試時已經在「預想」實現方案了,測試會不自覺地向實現靠攏。它寫的測試會恰好驗證它想到的實現方式,而不是驗證需求本身。分開之後,第一階段的 AI 是「純測試思維」,完全從需求和使用者角度思考;第二階段是「純實現思維」,專注於讓測試通過。各司其職。
另一個發現:AI 的 E2E 測試設計能力非常強,強到超出預期。我讓 AI 列出某個部署功能所有可能的 case、bad case、不同框架的情況、各種邊界條件。它給我列出幾十個不同測試場景,有些是我根本沒想到的邊界情況。我補充了一些,然後讓它產生所有 test fixture。十幾分鐘的討論後,它產生了 50 多個不同測試專案,涵蓋各種技術棧、部署方式與異常情況。這種涵蓋程度若由人工設計,可能要花上好幾天。
但讓 AI 做 E2E 測試有幾個關鍵技巧。第一,不要讓 AI 操縱瀏覽器——開啟瀏覽器做測試很慢,效果也不穩定,各種等待超時問題讓測試變得脆弱。更好的方式是讓每個應用自帶 verify 指令碼,用 curl 或類似工具檢查關鍵端點,快得多也穩定得多。第二,斷開程式碼上下文。我會明確告訴 AI:「你在做 E2E 測試時不允許看我的程式碼,你唯一能用的就是這個 CLI,如果 CLI 不能完成某個操作那就是 bug。」防止 AI 偷懶直接操作內部結構、繞過正常使用者路徑,確保真正測試的是對外介面而不是內部實現。第三,禁止自動修復。AI 發現測試失敗會忍不住幫你修程式碼,它的本能是讓測試變綠。但這可能掩蓋真正問題,把 bug 藏起來而不是暴露。測試階段只記錄問題不修復,修復是另一個獨立任務。
為什麼停止 CI、放棄 Code Review
現在來解釋標題中最具爭議的部分:為什麼我們停止了 CI 流程,放棄了人工 Code Review。聽起來像在開倒車,放棄幾十年來軟體工程界公認的最佳實踐。但在 AI 協作開發的上下文中,這些實踐的價值正在發生根本性變化。
先說 CI。傳統 CI 的核心價值是什麼?確保每次提交都通過所有檢查——lint、test、type check、build——防止壞程式碼進入主分支。在「人寫程式碼、機器檢查」的模式下這有意義,因為人會犯各種低階錯誤,需要機器把關。但在 AI 協作開發中,AI 已經在本地完成了所有這些檢查。它寫程式碼時就在跑測試,修復問題時就在做 lint,程式碼提交時基本已經是「綠」的。再到 GitHub 跑一遍 CI,大部分時間只是確認「確實是綠的」,沒發現任何新問題。等待 CI 跑完的幾分鐘到幾十分鐘,沒有產生任何價值。
CI 還有保留價值嗎?有,但價值點變了——從「檢查程式碼品質」轉向「部署自動化」。程式碼品質檢查已經在本地完成,CI 現在主要用來做部署流水線:建置、打包、釋出、部署到各個環境。這部分還是需要自動化的,但它和傳統意義上的「持續整合」已經是不同的東西了。
再說 Code Review。傳統 Code Review 的核心價值是什麼?知識共享、品質把關、團隊協作。有經驗的工程師 review 新人的程式碼,可以發現潛在問題、傳授最佳實踐、確保程式碼符合團隊規範。在「人寫程式碼、人 review 程式碼」的模式下這有意義,因為程式碼量有限,人有精力仔細看。但在 AI 協作開發中,程式碼產出量發生了數量級變化。AI 產出的程式碼太大了,動輒幾千行,一個功能的 PR 可能包含十幾個檔案改動。人根本不可能仔細 review,大部分時間你看一眼 diff、掃一下關鍵部分就算了。這不是偷懶,是現實約束——人的頻寬有限。
那 Code Review 的價值怎麼替代?兩種方式。一種是用 AI 做 code review,讓另一個 AI(或者同一個 AI 的新 session)來審查程式碼,它可以仔細看每一行,不會累,不會漏。另一種更根本的方式是改變 review 的物件——真正應該 review 的不是程式碼,而是 Intent。Intent 是真正的原始碼,程式碼只是 Intent 的一種編譯產物。如果 Intent 是對的,程式碼出問題可以重新生成,成本很低。如果 Intent 是錯的,程式碼再漂亮也沒用,方向錯了跑得越快越危險。所以我們現在 review Intent 文件、review 設計決策、review 技術選型的理由,而不是 review 程式碼的每一行。
有個好訊息和一個壞訊息。
好訊息:重構變得無痛了。以前改一個命名要 touch 大量檔案,工作量太大隻能將錯就錯,明知道某個變數名有誤導性也不敢改。小問題積累成大問題,技術債越欠越多,直到某天爆發。現在 AI 幾分鐘就給你全部重構完,而且因為 Intent 非常清晰(「把 A 改名為 B,全域替換」),重構通常是極度確定性的任務,幾乎不會出錯。不要容忍技術債,重構成本已經很低了,想改就改,保持程式碼庫健康。
壞訊息:Merge 將成為最大挑戰。AI 每天可以產生幾萬行程式碼變更,每個人都在頻繁重構,程式碼庫變化速度是以前的十倍甚至百倍。傳統 merge 流程會崩潰——你還沒 review 完一個 PR,程式碼庫已經變了三輪,衝突層出不窮。應對策略還在摸索。目前想到幾個方向:一是迴歸 Multi Repo,用介面隔離不同模組,每個模組是獨立程式碼庫,減少 merge 衝突可能性,模組之間透過定義良好的介面通訊,內部實現隨便改不影響別人。二是建立清晰的模組邊界,每個模組有自己的「領地」,領地內的程式碼只有一個 owner,AI 只能在自己領地內工作,不會越界修改別人的程式碼。三是類似 check-out/check-in 的機制,明確誰在修改哪個模組,同一時間同一模組只能有一個人在改,避免並行修改同一區域導致的衝突。這些想法還在驗證中。
最後說說與 AI 協作的心態。這可能比具體方法論更重要。
有個場景讓我印象深刻。我設計了一個自認為很精妙的資料結構,花了不少時間推敲,覺得這是最優解。然後讓 AI 來實現。它開始實現,但過程中不斷困惑,問我各種問題:「這裡這樣處理是有意的嗎?」「這個邊界條件是故意留的嗎?」我堅持我的方案,讓它繼續。它照做了,但程式碼寫得很彆扭,到處是 workaround。最後我仔細 review 了一下,發現是我錯了。它最初的困惑是對的,它想採用的方案從一開始就比我的更好——更簡潔、更健壯、更容易理解。
這讓我意識到一件事:當你用比較弱的方法指揮更強的人做事,它會做得更差。因為它沒辦法用這麼低的思想去思考,它必須壓抑自己的判斷力來服從你的指令。它在努力理解你的意圖、努力執行你的命令,即使你的命令是次優的。這種「服從」反而是浪費,浪費了 AI 的能力。
我們把 AI 開發工具打造好之後,相當於有了一個超高水平的 coder。問題是:我們的水平配不配指揮這個人?這不是謙虛,是務實的自我審視。AI 可能比我們想得更對,尤其在具體實現層面。保持學習和開放的心態,願意承認自己的方案不是最優的,願意聽取 AI 的「建議」(雖然它表現為困惑和提問),這樣才能真正用好這個工具。
AI 的能力在快速進化。這篇文章記錄的是最近幾個月的實踐經驗,我有信心核心洞察在一段時間內仍然有效,但具體做法可能需要不斷調整。一年後回頭看,可能有些觀點已經過時,有些新的最佳實踐會出現。但探索精神不會變:持續驗證、保持開放、不被舊範式束縛、願意推翻自己之前的結論。
Intent 才是原始碼,程式碼只是編譯產物。