實戰(zhàn)指南)
最近好幾個朋友來找我聊vibe coding聊到最后都會冒出同一個問題AI寫代碼確實快但為什么項目越寫越亂甚至到了自己都不敢改代碼的地步如果你也有這種感受那這篇內(nèi)容大概率對你有用。我過去半年一直在折騰vibe coding的各種玩法把AI當作結(jié)對編程搭檔也踩了不少坑后來逐漸把重心轉(zhuǎn)向spec-driven的方案也就是“先寫規(guī)格再讓AI照著實現(xiàn)”。這篇文章會把vibe coding的典型痛點、spec-driven的核心思路、完整實操流程以及我記錄下的問題排查經(jīng)驗一次性講清楚適合正在用AI輔助開發(fā)、又苦于代碼失控的開發(fā)者參考。1. 先搞清楚vibe coding到底哪里讓人又愛又恨1.1 vibe coding的真實體驗爽是爽翻車也是真的快所謂的vibe coding簡單說就是讓AI進入高比例自主生成代碼的狀態(tài)開發(fā)者更多負責描述想法、審查結(jié)果、修正方向而不是逐行手寫邏輯。你給一句“幫我寫個讀取溫濕度傳感器的驅(qū)動”AI能立刻給你吐出一套完整實現(xiàn)包括寄存器配置、數(shù)據(jù)解析、錯誤重試機制乍一看還挺像模像樣。我最初用的時候確實是爽尤其是做嵌入式原型驗證原來寫一個外設(shè)驅(qū)動至少要半天現(xiàn)在十分鐘就能跑起來樂高一樣的拼接體驗讓人上癮。但問題也隨之而來AI生成代碼的“舒適區(qū)”是模式化邏輯一旦項目涉及復雜狀態(tài)機、并發(fā)控制、硬件時序約束它就開始出現(xiàn)各種想當然的假設(shè)而這些假設(shè)往往是事后才暴露的因為在寫代碼的那一刻你根本不會去逐行校驗它到底為什么這么寫。我統(tǒng)計過自己一個月的實際工作流用vibe coding寫的代碼里大概有三分之一的時間花在了“修AI生成代碼產(chǎn)生的邊界問題”上而不是在推進功能本身?!翱臁弊兂闪隋e覺因為你在辨別哪些代碼是能用的、哪些是幻覺出來的反而消耗了大量精力。1.2 痛點的本質(zhì)代碼有“感覺”產(chǎn)品沒“共識”再往深一層看vibe coding最大的問題其實是缺少一個穩(wěn)定的約束錨點。普通開發(fā)流程里需求文檔、接口定義、驗收標準這些產(chǎn)物天然就是約束你按著文檔寫代碼再怎么跑偏也能拉回來。但vibe coding里對話記錄和AI的記憶就是全部的上下文而它們都是會漂移的。你今天下午和AI說“這個字段表示毫秒”隔兩天再開話題它可能就默認是秒了這種上下文丟失幾乎每隔幾次迭代就要出現(xiàn)一次。更麻煩的是多人協(xié)作的時候“感覺”是無法同步的。你用vibe coding寫了一個模塊同事拿到代碼根本不知道原始需求是什么AI當初為什么選這個方案、什么邊界條件是特意處理的全都藏在對話歷史里他要完整考古一遍才能動手改。所以我把vibe coding從小項目延展到稍大一點的項目之后發(fā)現(xiàn)自己真正缺的不是寫代碼的速度而是一個能把“我要什么”講清楚、并且能被機器和人都確認的中間層。1.3 不是要放棄vibe coding而是要給它戴上“籠頭”先說結(jié)論vibe coding本身沒有錯錯的是裸奔式使用。AI需要被約束而spec-driven正是目前我用下來最靠譜的約束方式。它不要求你回到傳統(tǒng)開發(fā)那種“萬事寫文檔”的重流程而是要求你在讓AI動手生成代碼之前先把行為契約、輸入輸出、異常情況、驗收標準這些關(guān)鍵要素寫清楚。我現(xiàn)在的開發(fā)習慣已經(jīng)固定成兩步走第一步用一份spec把需求和邊界定義清楚第二步再把spec喂給vibe coding工具讓它嚴格按照規(guī)格實現(xiàn)。這么做之后AI的自由發(fā)揮被限制在了一個合理的范圍里而我和同事之間的溝通成本也大幅下降因為大家討論的是規(guī)格而不是“AI怎么想的”。2. spec-driven的核心思路與方案設(shè)計2.1 一句話講清spec-driven到底是什么簡單來說spec-driven就是“先寫規(guī)格說明書再寫實現(xiàn)代碼”但這里的規(guī)格不是那種幾百頁的傳統(tǒng)需求文檔而是面向行為、可驗證、無歧義的技術(shù)契約。傳統(tǒng)的需求文檔關(guān)注“業(yè)務(wù)上要什么”比如“用戶能查看設(shè)備實時溫度”而spec-driven的規(guī)格更接近工程設(shè)計里的接口定義要寫清楚輸入是什么、輸出是什么、什么情況下會報錯、錯誤處理路徑是怎樣的。它的讀者有兩類一類是AI用來生成符合規(guī)格的代碼一類是人用來評審、驗收、維護。我經(jīng)常拿做菜打比方。傳統(tǒng)需求文檔像是告訴廚師“做一道好吃的魚”而spec是“準備一條500克左右的鱸魚蒸8分鐘出鍋淋上熱油和蒸魚豉油要求魚肉不散、無腥味”。后者才是可執(zhí)行、可檢驗的標準。2.2 spec-driven方案選型背后我為什么選它當初在糾結(jié)要不要引入spec-driven時我對比過三個方向一是繼續(xù)裸用vibe coding靠對話約束二是回到傳統(tǒng)的測試驅(qū)動開發(fā)先寫測試再實現(xiàn)三才是spec-driven。裸用vibe coding的問題前面已經(jīng)說了上下文漂移、無法協(xié)作、邊界失控。傳統(tǒng)TDD其實是個好方案但對AI開發(fā)有個尷尬的地方測試本身也要人來寫而且寫測試的過程往往比寫實現(xiàn)還費腦很多人根本堅持不下去。spec-driven正好卡在兩者之間它的規(guī)格文檔既可以當作生成代碼的輸入又可以當作寫測試的依據(jù)一舉兩得。從實操效率看寫一份規(guī)格的時間成本大概是我直接寫代碼的三分之一但能把返工率從百分之六十降到百分之二十左右。這個投資回報率非??捎^尤其是對于需要迭代的項目省下來的是后期調(diào)試和重構(gòu)的時間。2.3 spec-driven方案的核心構(gòu)成模塊一個完整的spec-driven開發(fā)閉環(huán)由四部分構(gòu)成行為規(guī)格定義功能的輸入、輸出、異常處理、邊界條件這是核心部分。AI生成代碼時主要依賴這部分內(nèi)容。驗收標準明確什么樣的實現(xiàn)算是完成通常用可量化的指標描述比如“解析結(jié)果誤差不超過0.5攝氏度”“超過3次重試后返回超時錯誤”等。非功能約束寫下性能要求、內(nèi)存限制、依賴限制等防止AI在實現(xiàn)時引入你不需要的東西比如嵌入式場景常見的實時性要求。變更記錄維護規(guī)格的版本歷史每次需求變化時先改規(guī)格再改代碼這樣項目的演進過程完全可追溯。這四部分不是要都寫到最細而是根據(jù)項目復雜程度靈活取舍。我自己做嵌入式小項目時重點寫行為規(guī)格和驗收標準非功能約束只寫硬指標變更記錄用最簡單的Changelog形式。2.4 什么樣的項目最適合用spec-driven根據(jù)我這半年的實踐有幾種項目類型用spec-driven收益特別大硬件相關(guān)開發(fā)尤其是嵌入式固件。因為硬件邏輯一旦寫錯輕則數(shù)據(jù)不對重則燒板子必須前置定義清楚。接口封裝類工作。比如給你的代碼庫設(shè)計對外API或者驅(qū)動模塊給上層提供接口規(guī)格就等于接口文檔一舉兩得。多人協(xié)作的AI輔助開發(fā)。規(guī)格是大家共同的基準線誰改了什么、為什么改看規(guī)格歷史一清二楚。需要長期維護的模塊。AI生成的代碼如果不做約束三個月后回頭看跟天書一樣有規(guī)格至少能幫你快速理解設(shè)計意圖。反之如果只是臨時腳本、一次性原型驗證、極其簡單的工具函數(shù)那直接用vibe coding裸寫就行了引入spec反而是過度設(shè)計。3. 從零搭建一套spec-driven的完整實操流程3.1 第一步先寫一個麻雀雖小五臟俱全的spec模板直接把我用到的mini版規(guī)格模板放出來大家可以照著改格式不需要花哨重點是把該鎖死的信息鎖死。# 功能規(guī)格溫濕度傳感器數(shù)據(jù)讀取模塊 ## 1. 功能概述 本模塊負責通過 I2C 接口讀取 SHT30 溫濕度傳感器數(shù)據(jù) 轉(zhuǎn)換為物理量后通過回調(diào)上報。 ## 2. 接口定義 - 初始化函數(shù)void sht30_init(I2C_HandleTypeDef *hi2c) - 讀取函數(shù)int sht30_read_temperature(float *temp, float *humidity) - 輸入無 - 輸出temp 為溫度值(攝氏度)humidity 為濕度值(百分比) - 返回0 表示成功-1 表示 I2C 通信失敗-2 表示數(shù)據(jù)校驗失敗 ## 3. 行為規(guī)則 - 讀取超時時間固定為 100ms - I2C 連續(xù)失敗 3 次后返回 -1 - 校驗失敗時不修改輸出參數(shù)的值 - 溫度計算誤差不超過 ±0.5 攝氏度 ## 4. 邊界與異常處理 - 傳感器未應答返回 -1 - 校驗和錯誤丟棄本次數(shù)據(jù)返回 -2 - 參數(shù)為空指針直接返回 -3 ## 5. 驗收標準 - 使用模擬 I2C 數(shù)據(jù)驗證時正確解析溫度、濕度數(shù)值 - 模擬斷線場景能穩(wěn)定返回 -1 - 連續(xù)讀取 1000 次無死鎖或異常崩潰這份模板看著簡單但實際寫的時候有幾個坑要注意。一是輸出參數(shù)別寫“成功時賦值”要把失敗場景下參數(shù)的行為也定義清楚因為這個直接決定調(diào)用方如何防御性編程。二是錯誤碼最好統(tǒng)一規(guī)劃別讓AI自己發(fā)明錯誤碼否則不同的模塊之間錯誤碼糾纏在一起排錯會讓人崩潰。3.2 第二步把spec喂給vibe coding工具的正確話術(shù)寫好了spec接下來就是讓AI干活。我試過直接丟整篇markdown給AI也試過拆成片段逐步喂實測下來拆成功能點逐步喂的效果最穩(wěn)。比如上面的規(guī)格文件我不會一次性全都給AI而是先給“接口定義”部分讓它寫出函數(shù)簽名和基礎(chǔ)框架然后追加“行為規(guī)則”讓它填充重試邏輯和錯誤處理最后給“邊界與異常處理”讓它補齊防御性代碼。這個過程用一句話引導就行“請按照附錄spec實現(xiàn)以下內(nèi)容嚴格遵守接口定義和錯誤碼約定。不確定的地方先提出疑問不要自行假設(shè)?!碧貏e強調(diào)“不確定的地方先提出疑問”這半句因為AI最容易犯的毛病就是遇到規(guī)格沒寫清楚的地方自動腦補一個合理值。它腦補的時候往往會跟你的真實需求發(fā)生偏差與其事后返工不如讓它先開口問。還有一個細節(jié)寫spec的時候要刻意留一部分內(nèi)容不寫比如具體用什么校準算法把這個留給AI去發(fā)揮。規(guī)格不是要把AI當無腦打字機恰恰相反要給它留一點可選空間這樣它能給出的方案往往比你預設(shè)的更好。3.3 第三步規(guī)格驅(qū)動的驗證閉環(huán)代碼生成完不等于閉環(huán)結(jié)束還需要驗證。我現(xiàn)在的驗證流程分三層第一層是讓AI自己對照規(guī)格做一次審查具體做法是把spec和代碼一起發(fā)回去請它對每一項行為規(guī)則核查實現(xiàn)是否一致輸出一張對照表。這個方法很實用能把大部分明顯漂移抓出來。第二層是拿規(guī)格寫測試。怎么從規(guī)格快速生成測試我一般用兩種方式一是讓AI按照驗收標準生成單元測試二是自己手動覆蓋幾個關(guān)鍵邊界。比如在上面的例子里“參數(shù)為空指針返回-3”這條就是必測項幾乎是白送的測試用例。第三層是代碼評審。評審時不用去摳AI生成了什么代碼而是看它有沒有偏離規(guī)格。我們有句口頭禪“不帶規(guī)格看代碼就是耍牛氓?!蓖轮g評審時手里一定要握著最新的規(guī)格文檔逐條比對這個習慣建立之后評審質(zhì)量提升極快。3.4 規(guī)格與代碼的同步演進策略實際操作中需求變更是家常便飯。我強烈建議定一個死規(guī)矩任何需求變更先改spec再談改代碼。哪怕只是把一個字段名從temp改成temperature都應該先更新規(guī)格再用“請根據(jù)最新spec更新實現(xiàn)”的指令讓AI做同步。這么做的原因很簡單規(guī)格是唯一的真相源。如果反過來先在對話里改了需求AI改了代碼然后你忘了更新規(guī)格那下次任何人包括AI自己再讀這個模塊看到的分裂狀態(tài)會直接讓上下文混亂。一次兩次可能無所謂積累多了代碼庫就變成了一個思路上的“縫合怪”。我在Git項目里會把spec文件放在docs/specs目錄下每個模塊對應一份markdown和源碼一起提交這樣規(guī)格和代碼始終在同一個提交記錄里追溯起來一目了然。3.5 工具鏈搭配markdown就夠了別盲目上重型平臺關(guān)于spec-driven的工具選型我先把結(jié)論放在前面不要一上來就上一堆平臺化工具最樸素的markdown加Git版本控制已經(jīng)能覆蓋大多數(shù)項目需求了。我試過用Notion管理規(guī)格也試過用專門的API設(shè)計工具但最后都回到了本地markdown文件。原因是規(guī)格文檔最重要的是貼近代碼、便于diff、方便AI讀取這三樣是云端文檔工具很難同時滿足的。markdown文件天然滿足這些要求可以放在代碼庫里面可以查看歷史變更可以直接被AI讀取。如果你的項目確實需要更結(jié)構(gòu)化的規(guī)格管理可以考慮給markdown增加一些約定比如固定模板、統(tǒng)一字段命名、用表格寫接口參數(shù)而不是引入新工具。工具越多維護負擔越重最終結(jié)果往往是規(guī)格文檔沒人更新又回到了裸code的狀態(tài)。3.6 嵌入式場景下的spec-driven實踐補充因為熱詞里有嵌入式vibe coding這里補充一下我在這類項目里整理出來的特殊經(jīng)驗。嵌入式項目最棘手的是硬件約束比如寄存器時序、DMA通道分配、Flash大小限制。這些信息光靠AI的理解往往是模糊的必須在規(guī)格里明確寫清楚硬限制。例如“本芯片F(xiàn)lash共64KB固件當前占用約40KB新模塊不得超過8KB”這類信息AI看到之后就不會寫出奢侈的查表算法。另外嵌入式開發(fā)里中斷上下文和主循環(huán)上下文的區(qū)別也是AI重災區(qū)。AI經(jīng)常默認所有代碼運行在同一個上下文中拿memset、printf直接放進中斷服務(wù)函數(shù)里。為了規(guī)避這個問題我通常在規(guī)格里加一條“中斷服務(wù)函數(shù)中禁止調(diào)用阻塞函數(shù)和動態(tài)內(nèi)存分配”效果非常顯著。硬件相關(guān)的調(diào)試往往是規(guī)格寫不全面的地方所以嵌入式領(lǐng)域用spec-driven時還需要特別注意“硬件行為不確定的地方要先跑最小驗證代碼確認后再寫進規(guī)格”。4. 常見問題與排查技巧實錄4.1 問題一AI無視規(guī)格依舊自行發(fā)揮怎么辦這是我最開始遇到、也是幾乎每個轉(zhuǎn)spec-driven的人都會撞上的問題。明明規(guī)格里寫了“超時100ms”AI生成的代碼里卻變成了“超時500ms”而且注釋還寫得理直氣壯。排查思路大概是這樣的一是檢查喂給AI的上下文是否太長導致它遺漏了規(guī)格細節(jié)。對話窗口內(nèi)容一大AI的局部注意力容易集中在最后幾段text前面的設(shè)定就容易丟。處理辦法是把規(guī)格拆小一次只喂一個模塊的規(guī)格。二是試試在代碼塊注釋里直接嵌入關(guān)鍵約定比如在函數(shù)定義的注釋里重復一遍“讀取超時固定為100ms”讓AI在生成代碼的時候就近看到約束。三是用“請逐條對比規(guī)格并指出不一致之處”的指令做二次審查強制AI自己檢查矛盾點。這三板斧下來九成以上的無視規(guī)格問題都能解決。4.2 問題二規(guī)格寫太細AI變得束手束腳反而低效另一條很容易走的極端是規(guī)格寫得事無巨細連循環(huán)用什么變量名、函數(shù)內(nèi)部先做什么后做什么都規(guī)定清楚結(jié)果AI從“生成器”變成了“翻譯器”生成的代碼質(zhì)量反而更差。后來我把規(guī)格分成兩個等級一級是“契約級”比如接口定義、錯誤碼、邊界行為必須嚴格遵循二級是“建議級”比如用什么排序算法、怎么組織文件結(jié)構(gòu)允許AI自由選擇。遇到“建議級”內(nèi)容我會在規(guī)格里用“可以”“建議”之類的措辭遇到“契約級”內(nèi)容用“必須”“禁止”之類的強約束詞。AI對這兩類詞的分辨能力比我預想中強值得充分利用。4.3 問題三規(guī)格和代碼不一致該信誰項目進行到中后期常常會出現(xiàn)“規(guī)格是舊的代碼是新的”這種分裂狀態(tài)。這時候最忌諱的是直接改代碼去湊規(guī)格正確做法是先確認哪個版本的意圖才是你真實的意圖。我的操作習慣是當發(fā)現(xiàn)不一致的時候先翻Git提交記錄看規(guī)格和代碼誰先被修改。如果代碼先改了說明當時肯定有一個未記錄的需求變更需要補進規(guī)格如果規(guī)格先改了說明AI或人工實現(xiàn)沒跟上需要催代碼更新。等到規(guī)格和代碼重新對齊之后再決定下一步動作。這個流程雖然聽起來繞了一下但能避免很多拍腦袋決策導致的反復。4.4 問題四怎么判斷規(guī)格寫得“夠了”不少朋友問我到底寫到什么程度算“夠了”能開始動工了。我自己的標準是當看到這份規(guī)格的人或AI能在不看其他任何資料的條件下獨立做出符合預期的實現(xiàn)就算夠了。這里又得回到我前面的觀點規(guī)格不是要準確到每個if語句的分支而是要準確到“無法產(chǎn)生第二種理解”。一個實用的判斷方法是“角色扮演法”假裝自己完全不懂項目背景只拿規(guī)格問自己幾個問題——輸入合法嗎非法輸入怎么處理網(wǎng)絡(luò)中斷怎么辦數(shù)據(jù)異常怎么辦如果每一個問題都能從規(guī)格里找到明確答案那就可以放心交給AI去實現(xiàn)了。4.5 v2個進度難解的坑額外補充再記錄一個我剛開始實踐時踩的比較深的坑寫spec往往沒有寫代碼有成就感容易寫著寫著偷懶想跳過步驟直接讓AI干。這種時候強烈建議把“spec先行的原則”當作紀律來執(zhí)行哪怕只寫一個十五分鐘的迷你規(guī)格也不要直接裸聊需求。一旦允許了“這次先不寫spec直接改吧”的例外接下來就會有一有二最后整個規(guī)范就名存實亡了。我自己的辦法是給每個項目的spec文件設(shè)置一個“未完成禁止輸入”的紅線檢查spec沒有更新完就絕不把新需求發(fā)給AI這種“強制儀式感”把產(chǎn)出規(guī)范和實際使用了硬綁定起來。5. 關(guān)于工具選型和擴展的一些個人心得5.1 用AI輔助寫spec本身的效率就很驚人其實spec文檔的撰寫也可以交給AI來輔助生成。我長期用的一套手法是先用vibe coding的方式跟AI聊一遍需求讓它生成一份draft spec然后我再人工逐個字段調(diào)整、補邊界、加約束。畢竟人對硬件限制和業(yè)務(wù)規(guī)則的敏感度是AI比不了的但AI比人強的是語法組織能力和查漏補缺的速度兩者結(jié)合效率最高。舉個例子有一次我要寫一個串口協(xié)議解析模塊的規(guī)格如果自己從零寫至少要一個小時。我先讓AI根據(jù)我口述的協(xié)議幀格式生成草案然后我再花二十分鐘把邊界條件、錯誤碼、校驗方式補充完整整體半小時搞定而且覆蓋到的細節(jié)比純手寫更全。5.2 spec-driven和AI工具的未來組合潛力我目前正在嘗試的一個方向是把spec文件本身作為項目全局上下文的“錨點”讓AI在每次會話開始時自動加載對應模塊的規(guī)格文件然后基于規(guī)格展開工作。現(xiàn)在很多AI編碼工具已經(jīng)支持項目級上下文綁定把規(guī)范掛進去之后即使隔了很長一段時間再回來繼續(xù)做AI也保持著對模塊結(jié)構(gòu)的正確認知。在嵌入式這種對正確性要求高的領(lǐng)域我進一步在嘗試把spec轉(zhuǎn)成機器可校驗的格式例如直接轉(zhuǎn)成頭文件里的靜態(tài)斷言、寄存器定義等讓規(guī)范直接進入編譯期檢查。這個方向目前還在實驗但效果已經(jīng)讓我堅定了spec-driven路線值得持續(xù)投入。6. 最后分享幾條實操中養(yǎng)成的硬規(guī)矩第一永遠讓規(guī)格先走一步。哪怕要改一行代碼也先在規(guī)格里改掉對應的描述。第二不要信任AI的“我覺得應該”。只要規(guī)格里沒有明確規(guī)定的內(nèi)容AI自作主張的實現(xiàn)一律視為bug要么改代碼要么改規(guī)格并做好記錄。第三定時把規(guī)格文件當作rebase基準。每次代碼經(jīng)過較多增刪之后我會專門安排一次“規(guī)格對齊日”把spec和當前實現(xiàn)徹底核對一遍消除隱形漂移。第四在團隊協(xié)作中評審任何AI生成的代碼前先問一句“這代碼對應的spec是哪個版本”。答不上來的評審直接駁回流程到目前為止運轉(zhuǎn)得還算順暢。踩過這么多坑之后我的切身體會就是vibe coding更像是開一輛動力強悍的車跑賽道是爽但沒有路標和護欄很快就得翻。spec不是用來限制AI的枷鎖而是給AI和人都劃清了路線的導航它在保住速度的同時讓你知道自己正在往哪里去。