的代碼評(píng)審自動(dòng)化實(shí)踐)
1. 為什么我要做 open-code-review 這個(gè)項(xiàng)目干開發(fā)十多年我見過太多因?yàn)椤案牧艘恍写a”引發(fā)線上事故的現(xiàn)場。最典型的場景是這樣的一個(gè) PR 在 GitLab 上掛了三天沒人看第四天負(fù)責(zé)人急著發(fā)版自己點(diǎn)了個(gè) approve 就合并了結(jié)果這段代碼里的空指針異常直接打爆了生產(chǎn)日志。事后復(fù)盤的時(shí)候大家都很沉默因?yàn)檎l都清楚問題出在哪——評(píng)審環(huán)節(jié)形同虛設(shè)PR 掛在那里不是被認(rèn)真看過的只是被“路過”表態(tài)的。還有個(gè)問題更隱蔽有些人不是不想認(rèn)真評(píng)審而是根本不知道看什么。新人拿到一個(gè) PR打開 diff 看了一眼覺得“語法沒錯(cuò)、測試貌似過了”就點(diǎn)了通過。真正該關(guān)注的性能隱患、邊界條件、錯(cuò)誤處理、數(shù)據(jù)庫查詢量級(jí)、并發(fā)安全一個(gè)都沒看明白。代碼評(píng)審長期處于“有流程無標(biāo)準(zhǔn)、有系統(tǒng)無方法”的狀態(tài)。所以當(dāng)團(tuán)隊(duì)決定把代碼評(píng)審這件事認(rèn)真做起來的時(shí)候我沒有直接選一個(gè)商業(yè)工具抄作業(yè)而是動(dòng)手沉淀了一套可以跨團(tuán)隊(duì)復(fù)用的實(shí)踐方案取名叫 open-code-review。這個(gè)項(xiàng)目的定位很明確不去替代 GitHub、GitLab、Gitee 自帶的那套 Pull Request 能力而是在現(xiàn)有平臺(tái)之上把評(píng)審規(guī)則、檢查清單、自動(dòng)化攔截、效果度量全部標(biāo)準(zhǔn)化。評(píng)審靠不靠“人自覺”不再重要因?yàn)榱鞒虝?huì)幫團(tuán)隊(duì)兜底。這個(gè)項(xiàng)目適合誰我覺得所有被“評(píng)審走形式”折磨過的團(tuán)隊(duì)都值得參考。不管你是三五個(gè)人的小創(chuàng)業(yè)團(tuán)隊(duì)還是幾十上百人的研發(fā)部門核心思路是一樣的——把評(píng)審從“憑感覺”變成“按清單”把質(zhì)量把關(guān)從“事后救火”變成“過程攔截”。下面我把這套方案的設(shè)計(jì)思路、落地細(xì)節(jié)、踩過的坑完整寫出來希望能給正在折騰代碼評(píng)審流程的你一些啟發(fā)。2. 整體設(shè)計(jì)思路不造輪子而是把輪子拼成車2.1 核心痛點(diǎn)評(píng)審最大的敵人不是懶是“不知道看什么”在做 open-code-review 之前我先把團(tuán)隊(duì)過去半年的評(píng)審數(shù)據(jù)拉出來看了一遍。結(jié)果很有意思平均每個(gè) PR 的評(píng)審時(shí)長是 4.2 天其中 70% 的時(shí)間花在“等人看”上面真正閱讀代碼的時(shí)間平均不到 15 分鐘。更扎心的是評(píng)審意見里有一大半集中在代碼風(fēng)格、命名規(guī)范、注釋缺失這類基礎(chǔ)問題上真正涉及邏輯正確性、異常處理、性能隱患的評(píng)論不到三成。這個(gè)數(shù)據(jù)說明兩件事。第一評(píng)審被人為拖慢了這跟工具沒關(guān)系跟人的習(xí)慣有關(guān)系。第二評(píng)審的關(guān)注點(diǎn)跑偏了大家把寶貴的注意力浪費(fèi)在機(jī)器就能解決的問題上自然沒有精力去深挖真正要命的邏輯缺陷。所以 open-code-review 的第一個(gè)設(shè)計(jì)原則就出來了凡是機(jī)器能自動(dòng)檢查的絕對(duì)不讓人肉來做。代碼風(fēng)格讓 linter 管提交信息格式讓 commitlint 管覆蓋率變化讓覆蓋率工具管剩下的時(shí)間才能留給人類去思考那些機(jī)器想不明白的事——業(yè)務(wù)邏輯對(duì)不對(duì)、架構(gòu)設(shè)計(jì)合不合理、未來的擴(kuò)展性怎么樣。2.2 技術(shù)選型GitHub Actions Danger.js 自定義 CI 工作流技術(shù)選型這塊我糾結(jié)過很久。市面上的商業(yè)評(píng)審工具不少功能也確實(shí)強(qiáng)大但對(duì)于很多團(tuán)隊(duì)來說多一套工具就多一個(gè)賬號(hào)、多一個(gè)學(xué)習(xí)成本、多一份訂閱費(fèi)用而且代碼都托管在別人平臺(tái)上數(shù)據(jù)安全也是個(gè)顧慮。我更傾向于用團(tuán)隊(duì)已經(jīng)在用的基礎(chǔ)設(shè)施把評(píng)審能力“嵌入”進(jìn)去而不是引入一套新系統(tǒng)。最終選型組合是這樣的組件選型理由代碼托管GitHub / GitLab / Gitee團(tuán)隊(duì)已有基礎(chǔ)不額外引入平臺(tái)自動(dòng)化檢查GitHub Actions / GitLab CI平臺(tái)原生能力零額外成本智能評(píng)審輔助Danger.js一門 DSL能寫規(guī)則去自動(dòng)點(diǎn)評(píng) PR靜態(tài)掃描ESLint SonarQubeESLint 管 JS/TS 規(guī)范SonarQube 管質(zhì)量門禁提交規(guī)范commitlint husky在提交那一刻就攔截不規(guī)范行為文檔沉淀倉庫內(nèi) Markdown 模板評(píng)審清單和模板跟代碼放一起跟著版本走用 GitHub Actions 而不是單獨(dú)搭一套 Jenkins是因?yàn)榕渲煤唵?、維護(hù)成本低而且對(duì)于大多數(shù)中小團(tuán)隊(duì)來說平臺(tái)自帶的 CI 能力已經(jīng)綽綽有余。Danger.js 是這套方案里的關(guān)鍵角色它能在 CI 里跑一段腳本讀取 PR 的元數(shù)據(jù)標(biāo)題、描述、修改文件列表、評(píng)論等然后按照我寫的規(guī)則自動(dòng)發(fā)表評(píng)審意見。這么說可能有點(diǎn)抽象真實(shí)的場景是如果 PR 沒有關(guān)聯(lián) issueDanger 自動(dòng)貼一條提醒如果修改了核心模塊的代碼卻沒有新增測試Danger 自動(dòng)提出質(zhì)疑。人還沒開始看機(jī)器已經(jīng)把門檻守住了。2.3 流程設(shè)計(jì)一個(gè) PR 從創(chuàng)建到合并要走完五道檢查很多團(tuán)隊(duì)覺得評(píng)審流程就是“提 PR — 等人 approve — 合并”太簡陋了。open-code-review 把流程重新梳理成五道關(guān)卡每一道都對(duì)應(yīng)明確的檢查和職責(zé)提交前鉤子本地husky 攔截保證 commit message 格式正確、提交前自動(dòng)跑增量 lint 與單測。靜態(tài)掃描CIESLint、Stylelint 掃描代碼規(guī)范SonarQube 檢查重復(fù)代碼、圈復(fù)雜度、潛在缺陷。智能規(guī)則Danger檢查 PR 描述是否填寫、是否關(guān)聯(lián)任務(wù)單、變更規(guī)模是否過大、是否缺少測試文件。人工評(píng)審強(qiáng)制性至少一名核心維護(hù)者 approve且評(píng)審必須按模板填寫意見不能只點(diǎn)按鈕。門禁合并保護(hù)分支上述所有檢查通過后由機(jī)器人或 CI 狀態(tài)控制合并按鈕任何一環(huán)紅掉都不能合并。這五道關(guān)卡不是拍腦袋定出來的。在只有“人工 approve”的時(shí)期我們上線過一個(gè) bug起因就是有人改了公共工具函數(shù)評(píng)審的人只看了自己負(fù)責(zé)的那個(gè)模塊根本沒意識(shí)到公共函數(shù)的變更影響到了全項(xiàng)目。加了自動(dòng)化的變更影響分析后這種問題在第一道掃描就能被標(biāo)記出來。3. 核心細(xì)節(jié)拆解評(píng)審規(guī)則、模板與自動(dòng)化腳本怎么寫才有效3.1 評(píng)審清單人家不是不想看是不知道看什么代碼評(píng)審一個(gè)很現(xiàn)實(shí)的困境是評(píng)審者打開 PR 后盯著屏幕發(fā)呆不知道該從哪下手。open-code-review 的解決方案是提供一份結(jié)構(gòu)化的評(píng)審清單我把這么多年踩雷總結(jié)出來的檢查項(xiàng)全部放了進(jìn)去分門別類照著看就不會(huì)漏。我整理的評(píng)審清單核心部分長這樣功能邏輯這個(gè)改動(dòng)是否完整實(shí)現(xiàn)了需求有沒有遺漏的邊界場景用戶怎么觸發(fā)這段代碼錯(cuò)誤處理外部接口調(diào)用失敗怎么辦網(wǎng)絡(luò)超時(shí)有沒有兜底數(shù)據(jù)為空時(shí)會(huì)不會(huì)拋異常性能影響新增的循環(huán)會(huì)不會(huì)造成 O(n2) 復(fù)雜度有沒有在循環(huán)里查詢數(shù)據(jù)庫有沒有不必要的重復(fù)渲染并發(fā)安全共享變量有沒有加鎖線程池使用是否合理會(huì)不會(huì)出現(xiàn)死鎖或者競態(tài)條件兼容性數(shù)據(jù)庫遷移腳本能否回滾前端改動(dòng)是否兼容老瀏覽器接口變更是否影響舊版本客戶端可維護(hù)性:命名是否表意清晰有沒有留下注釋解釋“為什么這么做”后續(xù)維護(hù)者能看懂嗎這個(gè)清單不是讓評(píng)審者逐條回復(fù)而是引導(dǎo)他們的注意力集中在真正的風(fēng)險(xiǎn)點(diǎn)上。實(shí)踐證明給了清單之后評(píng)審意見的質(zhì)量明顯上升從“這里應(yīng)該加個(gè)空行”這種碎碎念變成了“這里如果 upstream 掛了我們?cè)趺炊档住边@種真正有價(jià)值的問題。3.2 PR 模板把“該提供的信息”變成“必須要填的字段”模板這個(gè)東西看著不起眼實(shí)際作用比想象中大得多。沒有模板的時(shí)候PR 描述五花八門有人只寫一句“修復(fù) bug”有人干脆什么都不寫評(píng)審者還得從代碼 diff 里自己猜改動(dòng)意圖非常消耗耐心。open-code-review 的 PR 模板設(shè)計(jì)成這樣的結(jié)構(gòu)### 背景 這個(gè)變更解決了什么問題為什么需要做這個(gè)改動(dòng) ### 變更內(nèi)容 本次改動(dòng)涉及哪些模塊核心邏輯是什么 ### 影響范圍 改動(dòng)影響哪些功能是否涉及公共代碼是否需要同步更新文檔 ### 測試說明 本地怎么驗(yàn)證的覆蓋了哪些用例有沒有補(bǔ)充自動(dòng)化測試 ### 自檢清單 - [ ] 代碼已通過 lint 和單元測試 - [ ] 已補(bǔ)充/更新必要的測試用例 - [ ] 已檢查性能影響和異常處理 - [ ] 無敏感信息泄露密鑰、token、個(gè)人數(shù)據(jù)看到區(qū)別沒有模板不是在增加負(fù)擔(dān)而是在幫助提交者把“腦子里的信息”顯性化。這些信息對(duì)評(píng)審者來說是必不可少的上下文沒有上下文就評(píng)審等于讓一個(gè)裁判不看比賽就打分。寫完模板的過程其實(shí)也是提交者自己重新審視一遍代碼的過程經(jīng)常有人寫著寫著就發(fā)現(xiàn)了自己的邏輯漏洞。3.3 Danger.js 規(guī)則讓機(jī)器人替你盯細(xì)節(jié)Danger.js 是這套體系里我最喜歡的一個(gè)組件。它能把很多原本靠人肉提醒的事情變成自動(dòng)化而且規(guī)則完全自己控制想怎么玩怎么玩。我在項(xiàng)目里配置的規(guī)則覆蓋了這幾個(gè)高頻場景// dangerfile.js const { danger, markdown, warn, fail, message } require(danger) // 1. PR 描述過短直接提醒 if ((danger.github.pr.body || ).length 30) { warn(PR 描述過短請(qǐng)補(bǔ)充背景、變更內(nèi)容和測試說明。) } // 2. 檢查是否關(guān)聯(lián) issue const issueRefRegex /#\d/ if (!issueRefRegex.test(danger.github.pr.body)) { warn(這個(gè) PR 沒有關(guān)聯(lián)任何 issue請(qǐng)補(bǔ)充關(guān)聯(lián)編號(hào)。) } // 3. 變更規(guī)模過大時(shí)提示拆分成小 PR const additions danger.github.pr.additions if (additions 600) { fail(本次變更新增超過 600 行建議拆分成多個(gè)小 PR 再提交。) } // 4. 修改核心模塊但沒有新增測試時(shí)提出質(zhì)疑 const changedFiles danger.git.modified_files const coreModules [src/core/, src/utils/, src/api/] const touchedCore changedFiles.some((f) coreModules.some((m) f.includes(m))) const hasTests danger.git.modified_files.some((f) f.includes(__tests__) || f.includes(.test.) || f.includes(.spec.)) if (touchedCore !hasTests) { warn(你修改了核心模塊但未發(fā)現(xiàn)對(duì)應(yīng)的測試文件更新請(qǐng)確認(rèn)是否已補(bǔ)充測試。) }這個(gè)腳本在 CI 里跑完會(huì)自動(dòng)把評(píng)論貼到 PR 下面效果等于一個(gè)不吃不喝的機(jī)器人同事每一行提醒都有明確的依據(jù)而且完全沒有情緒——你可以跟機(jī)器人吵但它不會(huì)生氣也不會(huì)記仇。當(dāng)然機(jī)器人的價(jià)值不在于替代人而在于把人的注意力從低價(jià)值問題上解放出來讓評(píng)審者可以專心做只有人類才能做的判斷。有人可能擔(dān)心規(guī)則寫多了會(huì)不會(huì)誤報(bào)太多反而把真實(shí)問題淹沒。這個(gè)擔(dān)心是對(duì)的我的經(jīng)驗(yàn)是 Danger 規(guī)則寧少勿多每條規(guī)則都要有實(shí)際的踩坑經(jīng)驗(yàn)支撐不要為了炫技寫一堆花里胡哨的規(guī)則。開頭寫 4-5 條能實(shí)打?qū)嵔鉀Q問題的就足夠了后面再根據(jù)團(tuán)隊(duì)反饋慢慢迭代。3.4 合并保護(hù)配置讓門禁真正硬起來規(guī)則寫好了CI 跑起來了但如果合并按鈕還是誰都能點(diǎn)前面做的所有工作都白費(fèi)。open-code-review 里的最后一道防線是分支保護(hù)規(guī)則這在 GitHub 里叫 Branch protection rules在 GitLab 里叫 Merge request approvals。GitHub 上我推薦的配置組合是這樣的Require a pull request before merging禁止直接推送到主分支Require approvals必須至少 1-2 個(gè)評(píng)審者批準(zhǔn)Dismiss stale pull request approvals when new commits are pushed新提交后舊 approve 自動(dòng)作廢Require status checks to pass before merging所有 CI 檢查必須通過Require conversation resolution所有評(píng)論對(duì)話必須 resolve 才能合并最后一條特別重要。很多團(tuán)隊(duì)的默認(rèn)配置是“approve 之后評(píng)論可以掛著不解決就合并”這就導(dǎo)致了一個(gè)怪現(xiàn)象評(píng)審者明明提出了問題提交者嘴上說“改好了”但評(píng)論一直掛著至于是否真的改好了沒有強(qiáng)制確認(rèn)。打開 conversation resolution 之后這個(gè)漏洞就堵上了——不解決完所有對(duì)話合并按鈕永遠(yuǎn)灰著。4. 實(shí)操落地從零到一接入 open-code-review 的完整過程4.1 倉庫初始化先把基礎(chǔ)設(shè)施搭好假設(shè)你要在一個(gè)已有代碼倉庫上落地這套流程我建議按下面的順序來操作。先別急著寫規(guī)則先把基礎(chǔ)設(shè)施搭好否則后面步步受阻。第一步創(chuàng)建目錄結(jié)構(gòu)和基礎(chǔ)文件。open-code-review 項(xiàng)目本身可以作為一個(gè)獨(dú)立倉庫管理規(guī)則和模板也可以直接把相關(guān)文件分布在你的業(yè)務(wù)倉庫里。我更推薦后者因?yàn)樵u(píng)審規(guī)則只有跟業(yè)務(wù)代碼放在一起才能跟著代碼演進(jìn)一起維護(hù)避免規(guī)則跟實(shí)際脫節(jié)。your-project/ ├── .github/ │ ├── PULL_REQUEST_TEMPLATE.md # PR 模板 │ ├── dangerfile.js # Danger 規(guī)則 │ └── workflows/ │ ├── ci.yml # CI 主流程 │ └── danger.yml # Danger 專用流程 ├── .husky/ │ ├── pre-commit # 提交前鉤子 │ └── commit-msg # 提交信息校驗(yàn) ├── commitlint.config.js # commit message 規(guī)則 └── .eslintrc.js # Lint 配置第二步配置 husky 和 commitlint。這一層的意義在于把問題攔截在源頭提交者在本地就能看到報(bào)錯(cuò)而不是等 CI 跑半天才發(fā)現(xiàn)問題。// commitlint.config.js module.exports { extends: [commitlint/config-conventional], rules: { type-enum: [2, always, [feat, fix, docs, style, refactor, test, chore]], subject-case: [0], }, }husky 側(cè)掛兩個(gè)鉤子pre-commit 跑 lint-staged 做增量 lint 和格式化commit-msg 跑 commitlint 校驗(yàn)提交信息。第三步配置 CI 主流程。GitHub Actions 的配置文件大致是這樣的結(jié)構(gòu)name: CI on: pull_request: types: [opened, synchronize, reopened] push: branches: [main] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - uses: actions/setup-nodev3 with: node-version: 18 - run: npm ci - run: npm run lint test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - uses: actions/setup-nodev3 with: node-version: 18 - run: npm ci - run: npm run test:coverage danger: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - uses: actions/setup-nodev3 with: node-version: 18 - run: npm ci - run: npx danger ci env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}需要注意的是Danger 在 CI 里的 token 有三個(gè)DANGER_GITHUB_API_TOKEN 或 GITHUB_TOKEN 都行GitHub Actions 會(huì)自動(dòng)注入 GITHUB_TOKEN所以不用額外配置。但如果你的倉庫是私有倉庫并且團(tuán)隊(duì)比較在意權(quán)限邊界可以單獨(dú)創(chuàng)建一個(gè)低權(quán)限 bot 賬號(hào)來發(fā)評(píng)論。4.2 規(guī)則配置從團(tuán)隊(duì)歷史事故里提煉規(guī)則這是整個(gè)項(xiàng)目里最有價(jià)值、也最需要花心思的一步。我不建議去網(wǎng)上找一份“最佳實(shí)踐清單”直接抄因?yàn)槊恐F(tuán)隊(duì)踩過的坑都不一樣。更好的做法是把過去半年線上故障和嚴(yán)重的評(píng)審漏判拉出來一個(gè)個(gè)分析當(dāng)時(shí)的評(píng)審過程中為什么漏掉了然后針對(duì)性地寫規(guī)則。舉個(gè)例子我們團(tuán)隊(duì)曾經(jīng)出過一次很嚴(yán)重的線上事故有人改了數(shù)據(jù)庫連接池的配置參數(shù)本來想加大最大連接數(shù)結(jié)果把初始化連接數(shù)寫超了導(dǎo)致服務(wù)啟動(dòng)時(shí)直接把數(shù)據(jù)庫連爆。這個(gè)改動(dòng)的 diff 只有一行而且那一行看起來人畜無害但險(xiǎn)就險(xiǎn)在配置項(xiàng)的名字非常相似。事故之后我們總結(jié)了一條規(guī)則如果你修改了配置類文件或環(huán)境變量相關(guān)代碼必須在 PR 描述里明確寫出“修改前值”和“修改后值”并說明為什么改。這條規(guī)則就變成 Danger 里的一個(gè)檢查項(xiàng)if ( danger.git.modified_files.some((f) f.includes(.env) || f.includes(config/)) ) { const body danger.github.pr.body || if (!/修改前|from:|before:/i.test(body) || !/修改后|to:|after:/i.test(body)) { warn(檢測到配置類文件變更請(qǐng)?jiān)?PR 描述中補(bǔ)充修改前后的詳細(xì)值對(duì)比。) } }類似這樣從實(shí)戰(zhàn)中長出來的規(guī)則才能讓團(tuán)隊(duì)心服口服。因?yàn)槊總€(gè)人都知道那條規(guī)則背后對(duì)應(yīng)的是一次血淚教訓(xùn)而不是某個(gè)外部專家憑空想象的教條。4.3 團(tuán)隊(duì)約定硬流程之外還需要對(duì)焦“軟共識(shí)”只有工具和規(guī)則是不夠的。open-code-review 落地之后我發(fā)現(xiàn)一個(gè)有意思的現(xiàn)象自動(dòng)化規(guī)則大家都愿意遵守因?yàn)闄C(jī)器不講情面但人工評(píng)審的質(zhì)量還是參差不齊有人習(xí)慣性點(diǎn) approve有人一句話不說直接鎖死 PR。后來我意識(shí)到問題出在缺少“軟共識(shí)”。所謂的評(píng)審文化不是一條規(guī)定能建立的。我在團(tuán)隊(duì)里推廣了幾個(gè)約定效果比預(yù)期好很多第一評(píng)審要在 24 小時(shí)內(nèi)響應(yīng)。不是要求馬上看完而是至少看一眼并回復(fù)“已開始評(píng)審”或者“今天忙不過來明天上午看”讓提交者心里有底避免 PR 石沉大海。第二評(píng)審意見要區(qū)分“必須改”和“建議改”。用前綴標(biāo)記比如[blocker]是必須要改的問題不開玩笑[suggestion]是可選優(yōu)化項(xiàng)提交者可以拒絕但要說明理由。這樣層級(jí)清晰不會(huì)因?yàn)橐欢研∫庖娧蜎]了關(guān)鍵問題。第三每個(gè) PR 必須有一個(gè)明確的第一評(píng)審人assignee。沒有第一責(zé)任人就沒有人為這個(gè) PR 的質(zhì)量負(fù)責(zé)沒人盯著的 PR 最容易爛尾。assignee 可以在創(chuàng)建 PR 時(shí)手動(dòng)指定也可以用 CODEOWNERS 文件自動(dòng)分配。# CODEOWNERS * backend-owner frontend-owner src/payment/ payment-team-lead第四定期輪換評(píng)審搭檔。長期固定一個(gè)人評(píng)審另一整個(gè)模塊容易形成思維定式看習(xí)慣了什么都覺得沒問題。輪換之后新人會(huì)問“為什么這里這么寫”這種“外行的問題”往往會(huì)暴露隱形的設(shè)計(jì)決策逼著提交者重新審視自己的代碼。4.4 度量與反饋用數(shù)據(jù)說話而不是憑感覺open-code-review 最后一塊拼圖是度量。說實(shí)話度量這件事很容易走極端有的團(tuán)隊(duì)一點(diǎn)度量不做有的團(tuán)隊(duì)把一堆亂七八糟的指標(biāo)貼墻上結(jié)果引發(fā)焦慮和攀比。我的建議是只關(guān)注幾個(gè)真正能反映結(jié)果質(zhì)量的指標(biāo)而且只用于團(tuán)隊(duì)自我改進(jìn)不用于績效考核。我目前覺得最有價(jià)值的幾個(gè)指標(biāo)評(píng)審響應(yīng)時(shí)長從 PR 創(chuàng)建到第一次有效評(píng)審意見的時(shí)間。太長了說明人手不夠或流程阻塞。PR 合并時(shí)長從創(chuàng)建到合并的完整周期。過長說明拆分粒度太大或需求太大。評(píng)審意見中被采納的比例低說明評(píng)審意見質(zhì)量有問題或者提交者太固執(zhí)兩種情況都需要介入。每千行代碼缺陷數(shù)上線后統(tǒng)計(jì)看流程改造前后是否有明顯變化。這些指標(biāo)我建議用 CI 的產(chǎn)物直接采集或者用 GitHub API 寫個(gè)小腳本定期拉取匯總# 簡單的上報(bào)腳本思路獲取合并的 PR統(tǒng)計(jì)從創(chuàng)建到合并的時(shí)長 gh pr list --repo owner/repo --state merged --limit 100 --json createdAt,mergedAt \ | jq [.[] | {title, durationHours: (((.mergedAt | fromdateiso8601) - (.createdAt | fromdateiso8601)) / 3600)}] \ | jq map(.durationHours) | add / length上面的命令輸出的就是最近 100 個(gè)合并 PR 的平均生命周期小時(shí)非常簡單但有效。度量不是目的通過數(shù)據(jù)發(fā)現(xiàn)流程瓶頸才是目的。比如我發(fā)現(xiàn)“評(píng)審響應(yīng)時(shí)長”指標(biāo)總是很高排查后才知道是 CODEOWNERS 自動(dòng)指派出了問題——某個(gè)模塊只指定了一個(gè)負(fù)責(zé)人那個(gè)人休假去了整個(gè)模塊的 PR 全都卡在他名下沒人接。后來改成每個(gè)模塊至少指定兩個(gè)負(fù)責(zé)人問題就解決了。5. 常見問題與排查實(shí)錄5.1 遇到過的典型問題速查表任何流程落地都會(huì)遇到抵抗和意外open-code-review 在實(shí)踐過程中也碰到過不少問題。我把最常見的幾個(gè)整理成了表格方便你直接對(duì)照排查。問題現(xiàn)象根本原因解決方案CI 一直不觸發(fā)Actions workflow 文件名不對(duì)或分支過濾寫錯(cuò)檢查.github/workflows目錄確認(rèn) on 配置包含目標(biāo)分支Danger 不發(fā)表評(píng)論token 權(quán)限不足或danger ci退出碼非 0確認(rèn)GITHUB_TOKEN的權(quán)限設(shè)置用danger ci --failOnErrors排查合并按鈕仍然可點(diǎn)分支保護(hù)規(guī)則未正確配置到倉庫 Settings - Branches 確認(rèn)規(guī)則已應(yīng)用注意舊 PR 不受新規(guī)則約束Lint 全量跑太慢沒有用 lint-staged 做增量檢查改用lint-staged只對(duì)暫存文件執(zhí)行 lint評(píng)審者習(xí)慣性點(diǎn) approve沒有強(qiáng)制要求填寫評(píng)審意見開啟 GitHub 的 code review 必須評(píng)論功能或在 Danger 里檢查 review 為空時(shí)提醒規(guī)則誤報(bào)太多Danger 規(guī)則寫得過于敏感對(duì)每條規(guī)則加白名單或閾值持續(xù)迭代規(guī)則提交者在本地繞過 husky--no-verify強(qiáng)行跳過鉤子靠 CI 層兜底本地鉤子是提醒不是安全屏障表格里最后一條最值得展開說。husky 本質(zhì)上是本地鉤子任何提交者都能用git commit --no-verify繞過去這是它的局限。所以完整方案里一定要有 CI 層做兜底比如在 CI 里重新跑一次 commitlint 校驗(yàn)或者 lint 檢查。本地鉤子的意義是讓大部分人少走彎路而不是防個(gè)別不守規(guī)矩的人。5.2 兩個(gè)真實(shí)問題的完整排查過程這里曬一個(gè)實(shí)際發(fā)生的排查過程幫大家建立問題處理的思路。有一次我們?cè)诮尤?Danger 的時(shí)候發(fā)現(xiàn) CI 里的 danger job 顯示綠色通過了但 PR 下面一條機(jī)器人評(píng)論都沒有。第一反應(yīng)是腳本邏輯有問題結(jié)果本地跑danger pr https://github.com/...又能正常輸出評(píng)論問題就壓縮到了 CI 環(huán)境。一步步排查先檢查了 Actions 日志發(fā)現(xiàn)danger ci這一行執(zhí)行的輸出里有一個(gè) warning提示Cannot find a danger token. Skipping message posting.。原因很清晰GitHub Actions 里雖然自動(dòng)注入了GITHUB_TOKEN但 Danger 在 ci 模式下讀取的是DANGER_GITHUB_API_TOKEN這個(gè)環(huán)境變量如果沒設(shè)置就會(huì)靜默跳過發(fā)評(píng)論的步驟。解決方式是在 workflow 里顯式加上環(huán)境變量映射env: DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}還有一個(gè)問題更隱蔽。團(tuán)隊(duì)里有位同事提交了一個(gè)純文檔改動(dòng)的 PR結(jié)果 CI 全部通過了但 Danger 卻 fail 了提示“變更規(guī)模過大”。查下來發(fā)現(xiàn)原因不是文件多而是danger.github.pr.additions統(tǒng)計(jì)的是整個(gè) PR 的增減行數(shù)而他提交的文檔里包含了一個(gè)很長的 SQL 導(dǎo)出的數(shù)據(jù)文件新增行數(shù)直接飆到了兩千多行。這個(gè) case 屬于規(guī)則設(shè)計(jì)時(shí)沒考慮到的場景我們?cè)谝?guī)則里加了一個(gè)白名單把以.sql、.lock、.min.js結(jié)尾的文件排除在統(tǒng)計(jì)范圍之外問題就解決了。5.3 避坑指南這五個(gè)坑我?guī)湍悴冗^了第一別在第一天就上全部規(guī)則??焖偕暇€會(huì)導(dǎo)致團(tuán)隊(duì)集體反感大家會(huì)覺得流程是來折騰人的。我會(huì)建議分三個(gè)階段第一周先上 PR 模板和 lint第二周上 Danger 基礎(chǔ)規(guī)則第三周開啟強(qiáng)制的合并保護(hù)。小步快跑讓團(tuán)隊(duì)逐步適應(yīng)。第二模板字段不是越多越好。字段太多會(huì)讓每個(gè) PR 的填寫成本極高最后大家都填“無”來敷衍。我的經(jīng)驗(yàn)是核心字段控制在 5 個(gè)以內(nèi)如果有信息確實(shí)需要但不想增加模板負(fù)擔(dān)可以放到 issue 模板里讓提交者提交時(shí)寫清楚。第三approve 會(huì)過期的設(shè)定必須開著。GitHub 提供了 “dismiss stale approvals” 選項(xiàng)我強(qiáng)烈建議打開。否則評(píng)審者周一 approve 了提交者周二又改了一版代碼舊 approval 依然有效等于改動(dòng)沒有經(jīng)過評(píng)審就直接合并了。這種拼配置的細(xì)節(jié)往往決定了流程的真實(shí)強(qiáng)度。第四自動(dòng)化規(guī)則不要寫得太執(zhí)著。機(jī)器判斷有天然的局限比如規(guī)則判斷“修改了測試文件”時(shí)如果一個(gè)人把測試文件重命名也算修改就會(huì)誤報(bào)。合理的策略是把自動(dòng)化當(dāng)作提醒用 warn 而不是 fail人工評(píng)審環(huán)節(jié)再?zèng)Q定是否攔截。第五記得給評(píng)審者和提交者都留出口。規(guī)則再完美也會(huì)有合理的例外。比如緊急 hotfix 需要繞過流程那就應(yīng)該提供一個(gè)明確的例外通道而不是讓人偷偷用--no-verify。我們?cè)趫F(tuán)隊(duì)里約定緊急修復(fù)可以直接在 PR 描述里加[hotfix]標(biāo)記CI 會(huì)跳過部分檢查但合并后必須在一個(gè)工作日內(nèi)補(bǔ)上測試。給規(guī)則開一個(gè)“白手套通道”反而比一刀切更容易被接受。6. 落地效果與實(shí)際數(shù)據(jù)這套流程到底改變了什么open-code-review 在我們團(tuán)隊(duì)跑了三個(gè)月的真實(shí)數(shù)據(jù)我覺得有必要曬出來。這里不是吹噓這個(gè)項(xiàng)目多厲害而是想說清楚一個(gè)樸素的道理流程本身不產(chǎn)生價(jià)值流程帶來的人們行為變化才產(chǎn)生價(jià)值。上線前我們統(tǒng)計(jì)了基線數(shù)據(jù)PR 平均合并周期 4.2 天評(píng)審中發(fā)現(xiàn)的邏輯類問題占全部問題的 12%線上事故中因評(píng)審漏判導(dǎo)致的比例約 60%。三個(gè)月后同樣的統(tǒng)計(jì)口徑下PR 平均合并周期降到 1.8 天評(píng)審中邏輯類問題占比提升到 37%線上事故中因評(píng)審漏判導(dǎo)致的比例降到 22%。有人可能會(huì)說周期變短是因?yàn)榇蠹也桓艺J(rèn)真評(píng)審了恰恰相反真正的原因是以前 70% 的等待時(shí)間被自動(dòng)化判斷消化掉了人工評(píng)審的時(shí)間反而更集中、質(zhì)量更高。邏輯類問題的占比提升反映的是評(píng)審者的注意力從格式、命名這些瑣碎問題上轉(zhuǎn)移到了真正影響系統(tǒng)健壯性的核心問題上。自動(dòng)化兜住了底線人力才敢往高處看。還有個(gè)隱藏收益是新人培養(yǎng)速度變快了。新同學(xué)以前要看老代碼怎么寫的、看評(píng)審意見猜團(tuán)隊(duì)風(fēng)格現(xiàn)在直接看 PR 模板、看 Danger 提示、看評(píng)審清單就能快速理解團(tuán)隊(duì)對(duì)代碼質(zhì)量的底線要求。這比請(qǐng)老同學(xué)講一個(gè)小時(shí)的“代碼規(guī)范”高效得多。7. 如果你想在自己團(tuán)隊(duì)落地我的幾點(diǎn)真心建議整個(gè) open-code-review 項(xiàng)目寫到這里我一直盡量避免把它包裝成一套“標(biāo)準(zhǔn)答案”。代碼評(píng)審這件事沒有銀彈不同團(tuán)隊(duì)的業(yè)務(wù)節(jié)奏、技術(shù)棧、人員構(gòu)成差別太大照搬任何方案都會(huì)水土不服。但如果要說有什么共性的東西值得借鑒我會(huì)總結(jié)成這樣幾句話。第一先診斷再開方。不要一上來就急著配工具先把團(tuán)隊(duì)最近兩三個(gè)月的 PR 瀏覽一遍看看時(shí)間都消耗在哪里問題主要集中在哪些類型上。有了數(shù)據(jù)基礎(chǔ)你才知道規(guī)則的優(yōu)先級(jí)怎么排。第二自動(dòng)化是解放評(píng)審者不是替代評(píng)審者。機(jī)器檢查、機(jī)器人評(píng)論、CI 門禁這些機(jī)制的價(jià)值在于把低價(jià)值勞動(dòng)從人類身上移走讓評(píng)審者把腦力用在高價(jià)值的判斷上。如果有一天我們發(fā)現(xiàn)機(jī)器人已經(jīng)能完全替代人類評(píng)審那我也舉雙手歡迎——不過至少目前代碼評(píng)審還是個(gè)需要人味兒的活。第三規(guī)則的迭代邏輯是“事故驅(qū)動(dòng) 數(shù)據(jù)驅(qū)動(dòng)”。每出一個(gè)線上事故就回顧評(píng)審環(huán)節(jié)能不能攔住每發(fā)現(xiàn)一個(gè)高頻誤報(bào)就調(diào)整規(guī)則閾值。這套機(jī)制跑起來之后規(guī)則不是越堆越多而是越來越精準(zhǔn)。最后再分享一個(gè)小技巧。如果你想讓團(tuán)隊(duì)愿意配合這套流程最好的辦法不是發(fā)全員郵件通知而是找一個(gè)大家公認(rèn)的質(zhì)量標(biāo)桿項(xiàng)目先試點(diǎn)等效果出來后讓數(shù)據(jù)說話。三個(gè)月后把“接入前后對(duì)比圖”扔到群里不需要你多解釋什么其他項(xiàng)目的同事會(huì)主動(dòng)來問你是怎么做的。我在實(shí)際推行過程中的體會(huì)是改變?nèi)说牧?xí)慣最好的工具不是制度而是可感知的價(jià)值。