格再寫代碼:OpenSpec讓團隊告別亂改與返工)
接到一個改代碼的需求你的第一反應(yīng)是什么大部分人下意識打開IDE找到相關(guān)文件開始讀代碼、改代碼。但我觀察到的那些配合成熟的團隊第一動作恰恰相反——他們先按下一個暫停鍵打開一個文檔把代碼改動之前要說清楚的東西先寫下來。這套動作在圈子里被概括成一個詞OpenSpec。說得直白一點OpenSpec不是某個高深框架也不是必須引入的第三方依賴它更像是一套開發(fā)紀(jì)律任何一個代碼改動不管大小先產(chǎn)出規(guī)格說明再動手寫實現(xiàn)。這個“先寫說明、后寫代碼”的順序就是優(yōu)秀團隊和普通團隊之間一道很微妙的分水嶺。代碼是最貴的一環(huán)也是最不該第一環(huán)出現(xiàn)的環(huán)節(jié)。這篇文章我想把我在實際項目中踐行OpenSpec的經(jīng)驗、碰過的壁、總結(jié)出來的流程模板完整分享出來給那些正在為“亂改代碼、反復(fù)返工、評審扯皮”頭疼的團隊一個可以直接抄作業(yè)的參考。1. 為什么先寫規(guī)格而不是直接改代碼1.1 代碼回答不了“為什么”很多人覺得代碼本身就是最好的文檔讀源碼就懂了。這句話對單人項目、短生命周期腳本勉強成立但對一個多人維護、存在了三五年的老系統(tǒng)來說基本是句空話。舉個我遇到過的例子。有次排查一個線上訂單狀態(tài)異常的問題代碼里有這么一段邏輯看起來是某個歷史需求臨時加的判斷注釋寫著“fix bug”。但這個bug到底是什么bug為什么要在這一層處理影響范圍多大代碼本身完全不會告訴你。最后翻了Git提交記錄翻到半年前的某個commit才從提交信息里拼出一點線索。要知道那個commit的提交信息寫的還是“update order status”根本看不出真實意圖。代碼只能回答“現(xiàn)狀是什么”它很難回答“為什么現(xiàn)狀是這樣”、“當(dāng)初為什么選這個方案”、“這個分支還有沒有存在的必要”。這些信息一旦缺失后人改代碼就只能靠猜。猜對了運氣好猜錯了就是線上事故的伏筆。所以優(yōu)秀團隊在改代碼之前寫規(guī)格本質(zhì)上是在補一份“決策上下文”——把代碼里看不出來的那部分信息用文檔形式固化下來。1.2 改代碼是貴的改文檔是便宜的經(jīng)濟學(xué)里有個概念叫轉(zhuǎn)換成本。改一份規(guī)格文檔幾分鐘、幾十分鐘的事但改代碼、提PR、走評審、合入主干、觸發(fā)構(gòu)建、回歸測試整個鏈條跑下來成本是文檔的上百倍。更可怕的是改錯方向——代碼寫完了才發(fā)現(xiàn)跟需求對不上前面所有工作量全部作廢還得背上技術(shù)債。把這兩者做個對比就非常直觀改動位置成本量級出錯后果規(guī)格文檔低分鐘級別改一版就好沒什么代價接口設(shè)計中小時級別涉及調(diào)用方改造成本代碼實現(xiàn)高天級別推翻重寫浪費嚴(yán)重已發(fā)布版本極高周級別線上回滾、數(shù)據(jù)修復(fù)、用戶投訴所以我一直給團隊強調(diào)一個觀念把問題消滅在規(guī)格階段是性價比最高的質(zhì)量手段。早期發(fā)現(xiàn)的邏輯漏洞改的只是一個句子晚期發(fā)現(xiàn)的邏輯漏洞改的是一整片代碼。1.3 先對齊“做什么”再討論“怎么做”團隊協(xié)作中最大的浪費不是某個人寫代碼寫慢了而是兩個人做出了完全不一樣的東西還都以為自己做的是對的那個。這就是典型的語義錯位——需求方說“加個導(dǎo)出”心里想的是導(dǎo)出全部字段開發(fā)者理解成“加個導(dǎo)出”做出來只導(dǎo)出了核心幾個字段測試拿著單子一驗發(fā)現(xiàn)跟預(yù)期對不上來回扯幾輪。OpenSpec的方式是把這個對齊動作前置。所有人都先圍繞一份規(guī)格說明達成共識要改什么不改什么邊界在哪里驗收標(biāo)準(zhǔn)是什么。這些在文檔階段談清楚成本極低。一旦進入編碼環(huán)節(jié)任何人都不許帶著自己的“理解”自由發(fā)揮。這不是壓制創(chuàng)造力而是確保創(chuàng)造力用在正確的方向上。1.4 規(guī)格是評審的“公共語境”代碼評審為什么容易流于形式最大的原因是評審者不了解背景。評審者看到一堆diff不知道前因后果就只能看看命名、看看格式提一些不痛不癢的建議。真正有價值的評審——業(yè)務(wù)邏輯是否正確、邊界條件是否覆蓋、設(shè)計方案是否最優(yōu)——反而沒人提因為大家沒有公共的討論基礎(chǔ)。有了規(guī)格說明評審的語境就完全不同了。評審者先讀規(guī)格再對照代碼自然能看出“實現(xiàn)是否忠實地覆蓋了規(guī)格里的每一條行為約定”。我個人的經(jīng)驗規(guī)格先行之后評審意見的質(zhì)量明顯上了一個檔次不再是一堆“建議重命名變量”而是真正在討論邏輯問題。2. 一份合格的規(guī)格說明到底寫什么2.1 不是設(shè)計文檔是行為契約很多團隊不是沒寫過文檔而是把規(guī)格說明寫成了設(shè)計文檔一上來就大談技術(shù)架構(gòu)、類圖、數(shù)據(jù)庫表結(jié)構(gòu)。方向偏了。OpenSpec瞄準(zhǔn)的是一份“行為契約”它回答的問題是這個改動之后系統(tǒng)對外表現(xiàn)的行為會發(fā)生什么變化。技術(shù)實現(xiàn)細節(jié)當(dāng)然可以寫但應(yīng)該放在行為約定之后作為輔助說明。讀者先理解了“要達成什么行為”再看“用什么技術(shù)手段實現(xiàn)”這才順暢。一上來就講底層設(shè)計讀者腦子里沒有“為什么需要這個設(shè)計”的錨點讀起來非常累。我用的模板保持著相對固定的結(jié)構(gòu)寫多了以后基本是肌肉記憶# 變更標(biāo)題 變更類型: 功能新增 / 缺陷修復(fù) / 性能優(yōu)化 / 重構(gòu) 影響范圍: 涉及模塊、服務(wù)、接口 作者: 日期: ## 背景與動機 為什么需要這個改動解決什么用戶問題當(dāng)前方案的痛點在哪 ## 范圍目標(biāo) 做什么不做什么邊界在哪目標(biāo)行為是什么 ## 行為變化 改動前后系統(tǒng)行為有什么不同用戶可感知的差異是什么 ## 接口與數(shù)據(jù) 涉及哪些接口參數(shù)怎么變數(shù)據(jù)結(jié)構(gòu)有沒有調(diào)整存儲邏輯變不變 ## 兼容性與風(fēng)險 會不會破壞現(xiàn)有功能有沒有歷史數(shù)據(jù)需要處理有沒有依賴方受影響 ## 測試計劃 怎么驗證這個改動核心用例是什么邊界情況怎么覆蓋這個模板不需要多么花哨但每個字段都有它的用途。評審的時候大家就是逐條對著這個結(jié)構(gòu)過效率非常高。2.2 一個好例子訂單導(dǎo)出功能光說模板太抽象我用一個常見場景舉個例子給訂單管理后臺增加一個批量導(dǎo)出功能。背景與動機運營同學(xué)每天都需要導(dǎo)當(dāng)天的訂單明細做對賬現(xiàn)在只能手動一頁頁復(fù)制既慢又容易漏。希望提供一個批量導(dǎo)出按查詢條件篩出結(jié)果一鍵導(dǎo)出Excel。范圍目標(biāo)支持按時間范圍、訂單狀態(tài)篩選后導(dǎo)出導(dǎo)出字段與列表展示字段保持一致只支持Excel格式暫不做CSV避免范圍膨脹不做定時導(dǎo)出、不做導(dǎo)出歷史管理明確不做防止被加料行為變化后臺訂單列表頁新增“導(dǎo)出”按鈕點擊后按當(dāng)前篩選條件生成文件并在右下角彈出下載任務(wù)。單次導(dǎo)出上限5000條超過時提示縮小篩選范圍。接口與數(shù)據(jù)新增后端接口/api/orders/export支持start_time、end_time、status參數(shù)導(dǎo)出任務(wù)異步執(zhí)行完成后生成臨時文件供下載臨時文件保留24小時后自動清理。兼容性與風(fēng)險查詢邏輯可復(fù)用現(xiàn)有列表查詢邏輯但需注意新增的臨時文件存儲目錄權(quán)限配置導(dǎo)出大批量數(shù)據(jù)時需要關(guān)注內(nèi)存占用流程上先走文件流寫入避免整表加載到內(nèi)存。測試計劃驗證按時間篩選導(dǎo)出、按狀態(tài)篩選導(dǎo)出、空結(jié)果導(dǎo)出、超過5000條提示、導(dǎo)出中重復(fù)點擊按鈕、導(dǎo)出后文件的字段完整性??吹竭@個規(guī)格一個完全不了解訂單模塊的開發(fā)者也能很清楚地知道要做什么、做到什么程度、有哪些坑要避開。規(guī)格文件替代了大量口口相傳的信息損耗。2.3 寫規(guī)格時最容易犯的三個錯誤第一個錯誤是只寫“做什么”不寫“為什么”。這樣的規(guī)格其實沒什么生命力過一段時間作者自己都忘了當(dāng)初為什么這么設(shè)計。第二個錯誤是過度細節(jié)化。每條規(guī)格寫得像需求說明書事無巨細把每個按鈕的文案、每個交互細節(jié)都固化下來。這會帶來兩個問題一是寫起來負(fù)擔(dān)極重堅持不下去二是讀起來負(fù)擔(dān)極重評審者很難抓重點。規(guī)格的顆粒度應(yīng)該保持在“行為契約”層面而非“UI驗收清單”層面。第三個錯誤也是最隱蔽的是不敢寫“不做什么”。很多人覺得多做點總比少做好于是把一些模棱兩可的需求也塞進來。結(jié)果就是工作量不斷膨脹工期失控。明確寫下“不做什么”是對整個團隊負(fù)責(zé)。3. 從規(guī)格到代碼一次完整的閉環(huán)實操3.1 第一步寫下動機期間不要碰方案OpenSpec流程的第一步很多人會忍不住直接跳到設(shè)計。我會強制自己先只回答一個問題為什么要做這次改動背景、痛點、訴求原原本本寫下來。這個階段禁止寫具體技術(shù)方案甚至不建議想技術(shù)方案。原因是方案思考會反過來扭曲對問題的理解。人一旦心里有了一個方案就會下意識把問題往這個方案上靠容易忽略其他可能性。先老老實實把問題定義清楚后面再談解決方案。實際操作中這個階段的產(chǎn)出通常只要幾百字寫清楚“現(xiàn)狀哪里不對”“期望變成什么樣子”就可以了。3.2 第二步拉人對齊允許所有人拍磚規(guī)格寫出來之后不是直接拍板進開發(fā)而是拉一次簡短的對齊評審把這個規(guī)格交給大家。我習(xí)慣的做法是把規(guī)格貼在群里或者評審文檔里給24小時的閱讀期然后約一個不超過30分鐘的會議。會議的重點不是“過一遍內(nèi)容”而是直接奔著問題去哪些邊界沒覆蓋、哪些行為描述有歧義、哪些地方超出了當(dāng)前版本范圍、有沒有更好的方案。每個人可以發(fā)表意見但有一條規(guī)則意見必須落到規(guī)格文件上要么改措辭要么增刪條目??陬^說說的意見會后就會被忘掉等于白說。這個環(huán)節(jié)我見過兩種極端一種是誰都不說話另一種是全員沉浸式摳細節(jié)。我一般會跟團隊說評審是找問題不是認(rèn)可你有問題說問題沒問題就過。對改動范圍大的規(guī)格我還會要求核心干系人書面確認(rèn)哪怕只是一行“我同意這個方案”。3.3 第三步方案細化給行為找到技術(shù)落點問題澄清之后規(guī)格進入技術(shù)設(shè)計階段。這一步的核心是把“行為契約”映射到具體的代碼改動位置哪個模塊、哪個接口、哪個數(shù)據(jù)結(jié)構(gòu)要變新增什么刪除什么。不一定要寫代碼級別的細節(jié)但改動的范圍要想清楚。我常用的做法是在規(guī)格文件里追加一個“實現(xiàn)方案”部分簡單描述技術(shù)思路即可。這里的關(guān)鍵是養(yǎng)成關(guān)聯(lián)的意識每一條行為變化都應(yīng)該能找到對應(yīng)的技術(shù)落點。找不到落點的行為是空談有落點卻不在規(guī)格里的行為是隱患。舉個例子那個導(dǎo)出功能的規(guī)格里有一條“異步生成文件”技術(shù)落點就是新增一個任務(wù)隊列或者復(fù)用現(xiàn)有的異步任務(wù)框架。這些落點寫清楚后開發(fā)時就是按圖索驥不用邊寫邊想應(yīng)該改哪里。3.4 第四步規(guī)格定稿之后編碼才啟動規(guī)格定稿是編碼工作的“開工許可證”。只要規(guī)格還沒有定稿任何人都不允許提前寫實現(xiàn)代碼哪怕只是搭架子也不行。這是我踩過大坑之后的切身教訓(xùn)有一次團隊里一個開發(fā)覺得需求簡單沒等規(guī)格確認(rèn)就直接上手改改完發(fā)現(xiàn)需求方想要的是另一種交互方式全部推翻。定稿的標(biāo)準(zhǔn)很簡單相關(guān)干系人都看過有異議的條目已經(jīng)解決并被記錄。規(guī)格文件在版本庫里歸檔后續(xù)所有的編碼、測試都以這個版本為基準(zhǔn)。3.5 第五步編碼與PR規(guī)格對照落地真正寫代碼的時候不需要再糾結(jié)“我要做什么這個邏輯放哪”這些都已經(jīng)在規(guī)格里定了。開發(fā)者的注意力可以全部集中在具體實現(xiàn)的質(zhì)量上命名、封裝、異常處理、性能細節(jié)。提PR的時候有一個習(xí)慣我一直保持著在PR描述里貼上規(guī)格文件的鏈接或者路徑如果實現(xiàn)了過程中對規(guī)格里有偏移的地方改了接口參數(shù)、調(diào)整了數(shù)據(jù)格式必須在PR里明確標(biāo)注并說明理由。這樣評審者一眼能看到實現(xiàn)與規(guī)格的差異而不是從diff里慢慢猜。有個細節(jié)值得注意實現(xiàn)過程中的規(guī)格變更其實是一種很常見的狀態(tài)需求理解深化后原定方案可能需要調(diào)整這很正常。但調(diào)整要走流程先改規(guī)格再改代碼讓規(guī)格始終是代碼的上游。一旦允許代碼反過來隨意改規(guī)格整個體系的信用就崩塌了。4. 常見問題與排查技巧實錄4.1 “緊急修復(fù)根本沒時間寫規(guī)格”這是聽到過最多的反對聲音線上故障十萬火急誰有時間寫規(guī)格我的回答是發(fā)生故障時的第一目標(biāo)是止血止血本身可以不走完整流程簡化到傻瓜式的“最小規(guī)格”就行——背景、改動點、影響范圍、回滾方案幾百字。這個最小規(guī)格不是走形式它有一個實實在在的作用幫你在高度緊張的狀態(tài)下想清楚改動的影響邊界。很多緊急修復(fù)翻車不是修錯了而是修的時候沒想全影響面。寫這個用不了十分鐘但這十分鐘能逼你把影響范圍過一遍腦子。事后必須補一份完整規(guī)格歸檔記錄故障的真實原因、修復(fù)方案、后續(xù)預(yù)防措施。這樣做的好處是防止同一個坑踩第二次。4.2 “寫了規(guī)格沒人看團隊流于形式”規(guī)格文件寫完往倉庫里一推然后就沒有然后了。這個問題非常典型根源多半在于規(guī)格沒有嵌入開發(fā)流程。如果寫不寫規(guī)格對開發(fā)沒有影響那自然會淪為形式。破解方法就是把規(guī)格和流程綁死PR必須附帶規(guī)格鏈接沒有規(guī)格的PR不進入評審評審時先評規(guī)格再評代碼定稿規(guī)格自動關(guān)聯(lián)需求單、里程碑代碼合入后規(guī)格即成為該功能的檔案后續(xù)任何改動都必須更新它說白了形式主義的大鍋不是團隊?wèi)卸橇鞒虥]有給規(guī)格留出不可跳過的位置。一旦規(guī)格成為流程必經(jīng)節(jié)點沒有人會再把過程走過場。4.3 “規(guī)格太詳細成了寫作文大會”規(guī)格寫得太長每次都像寫畢業(yè)論文這種團隊也很難堅持。要警惕一個傾向規(guī)格的詳細程度越高維護成本越大迭代速度越慢。規(guī)格不是越詳細越好而是越準(zhǔn)確越好。我根據(jù)變更規(guī)模和影響面來“分級對待”影響單個模塊內(nèi)部的小改動兩三段帶格式的說明就夠了跨模塊、涉及接口變更的改動走完整模板涉及架構(gòu)級決策的改動單獨開專項設(shè)計評審。規(guī)格是工具不是負(fù)擔(dān)怎么有效怎么來。4.4 規(guī)格與代碼漂移了怎么修代碼和規(guī)格不一致幾乎每個團隊都會遇到。漂移的原因是代碼改了規(guī)格沒同步更新。這類問題沒有銀彈唯一的辦法是“常態(tài)化糾正”每次review發(fā)現(xiàn)不一致就當(dāng)場修訂規(guī)格每個迭代末尾花一點時間核對關(guān)鍵模塊的規(guī)格與代碼發(fā)現(xiàn)失聯(lián)就補上。堅持幾個迭代團隊會養(yǎng)成同步更新的肌肉記憶。我個人問過一個團隊里的資深開發(fā)為什么不喜歡更新規(guī)格。他說改代碼是有明確反饋的改完就生效改規(guī)格沒有即時反饋總覺得是白干活。這個心理很真實所以我后來規(guī)定規(guī)格同步也是評審?fù)ㄟ^條件之一建立起“改了代碼不更新規(guī)格就扣分”的約束。時間久了大家就意識到維護文檔和寫代碼一樣是對未來負(fù)責(zé)。4.5 不寫規(guī)格的時候長什么樣做個對比經(jīng)歷過有規(guī)格和沒規(guī)格兩種狀態(tài)我對這個是深有體會沒有規(guī)格的狀態(tài)是需求口頭一說開發(fā)憑理解開工實現(xiàn)過程中發(fā)現(xiàn)理解錯了返工重來開發(fā)完成了測試按自己的理解驗驗出跟需求不一致來回撕扯上線一段時間后新開發(fā)接手讀代碼讀到一頭霧水不知道當(dāng)初為什么這么寫需求方和開發(fā)組為了“當(dāng)初是不是這么說的”各執(zhí)一詞沒有證據(jù)。有規(guī)格的狀態(tài)是需求講清楚寫下來對齊確認(rèn)開發(fā)按規(guī)格實現(xiàn)遇到疑問翻規(guī)格規(guī)格沒寫的地方先補規(guī)格再動手測試按規(guī)格寫用例規(guī)格覆蓋不到的地方說明測試盲區(qū)接手維護的人先看規(guī)格幾分鐘就理解了設(shè)計意圖和限制條件爭議發(fā)生時翻規(guī)格誰寫的、什么時候?qū)懙摹?dāng)初怎么定的一目了然。這兩套狀態(tài)我都真實經(jīng)歷過從混亂走向秩序的感覺就是團隊開始“變得可預(yù)測”。代碼質(zhì)量不只是靠技術(shù)更是靠流程逼出來的。5. 落地OpenSpec的幾個實操建議5.1 開始時要小步快跑先跑通再說一上來就想把所有項目、所有改動都納入規(guī)格流程很容易引發(fā)團隊反彈。建議先挑一個中等規(guī)模的功能改動從頭到尾走一遍流程寫規(guī)格、評審、定稿、開發(fā)、驗收。團隊的真實感受會告訴你這套流程適不適合哪些環(huán)節(jié)需要調(diào)整。流程需要先長出來再優(yōu)化。5.2 模板要放在倉庫里而不是聊天軟件里規(guī)格模板應(yīng)該跟代碼放在同一個版本庫里一級目錄放一個specs/下面按日期和主題建子目錄寫成Markdown。這樣做的好處格外實際規(guī)格跟代碼同源管理改起來能追蹤PR時可以直接引用路徑評審記錄、修改歷史全都沉淀在版本庫里。如果規(guī)格散落在聊天記錄或網(wǎng)盤里那基本等于沒寫。目錄結(jié)構(gòu)可以參考specs/ 2025-06/ order-export/ README.md cancel-refund-refactor/ README.md 2025-07/ user-center-migration/ README.md5.3 定義“規(guī)格完成”的強制標(biāo)準(zhǔn)我習(xí)慣用一連串問題做完成的自我檢查每個問題回答“是”才能走到下一步是否所有人都知道這次改動的背景是否明確列出了范圍和邊界是否描述了變更前后的行為差異是否標(biāo)明了風(fēng)險和兼容性考慮是否有人對當(dāng)前版本明確表示了異議如果任何一個問題是否定的那規(guī)格還不到火候。寫規(guī)格不需要特別的工具不需要重量級平臺。一個模板、一個評審習(xí)慣、一個流程約束就夠了。核心不在于工具多好而在于團隊是否認(rèn)可“先對齊、再編碼”這個基本原則。在我的實踐里OpenSpec真正帶來的改變不是“文檔變多了”而是“返工變少了”“疑問變少了”“扯皮變少了”。有一次我們做了一個跨模塊的重構(gòu)按照規(guī)格流程一步步走最終一次評審?fù)ㄟ^實現(xiàn)過程和設(shè)計完全一致上線后沒有任何回歸問題。放在以前這種規(guī)模的重構(gòu)通常要來回磨合好幾輪。這件事讓我確定了規(guī)格先行不是繁瑣的流程而是對團隊成員時間和精力的最大尊重。作為親歷者我建議你先別糾結(jié)這門方法叫什么名字從身邊最近的一次需求開始在寫第一行代碼之前先花二十分鐘把“為什么改、改什么、不做什么、風(fēng)險在哪”寫下來。然后拿著這份說明去問一下使用方、相關(guān)同事聽聽他們怎么說。做完這一步你會立刻體會到原來改代碼之前做的那件事省下來的遠不止一個晚上的加班時間。