首页 / 资讯中心 / 文章详情

open-code-review:用ESLint与Reviewdog构建开放代码审查工作流

open-code-review:用ESLint与Reviewdog构建开放代码审查工作流 ★ FEATURED ARTICLE
上个月我们线上出了一个不算严重但足够丢人的事故一个看起来人畜无害的配置项改动因为拼写错误和类型不匹配在灰度阶段把所有用户的偏好设置全重置了。事故复盘时发现这个改动是同事在周五下午趁大家不备提的review的同事草草扫了两眼就点了通过压根没注意到那行问题。我后来在组里说了一句话代码审查这种烂事靠自觉是靠不住的。于是我开始认真琢磨怎么把代码审查做成一套标准的、开放的、任何人装上就能用的工作流。这就是open-code-review这个项目的由来。open-code-review本质上不是某个单一工具而是一套把代码审查规则、自动化检查、CI流程、团队规范全部串起来的开源工作流方案。它能解决的核心问题很简单让每次代码提交在合并前就被机器和人双重把关把低级错误、规范问题、设计隐患提前拦在门口而不是等上线后靠用户来报bug。适合谁用适合被“review只是走过场”困扰的技术团队适合想在不增加人力负担的前提下把代码质量量化、流程化的工程效能负责人也适合个人项目想建立一点质量底线的独立开发者。这套方案不需要一次性全量照搬可以从最轻量的一两个环节开始落地跑顺了再逐步加码。1. 为什么需要一套开放的代码审查工作流1.1 代码审查被严重低估的价值很多人觉得代码审查就是找茬、挑刺甚至觉得它是流程负担拖慢了发布节奏。但如果你认真跑过一段时间的规范审查你会发现它真正解决的问题远不止“少几个bug”。代码审查能拦住那种“当时看着没问题、上线就出事故”的低级错误。人类reviewer的注意力是有限的尤其在周一早上或周五下午你不可能指望一个同事用10分钟把1000行diff里的每一行都仔细读一遍。机器不会累规则不会看走眼把这一类问题交给自动化等于给每次提交上了一份底线保险。代码审查还有一个经常被忽视的作用知识传递。组里新人写的代码老手在review时给出修改建议这是在真实代码上做师徒教学比任何培训文档都有效。但人肉review天然不稳定——遇到忙的时候、急的时候审查质量直线下降。用一套开放、统一的工作流把大家都该遵守的底线固化下来剩下的人力再去关注逻辑架构这类机器判断不了的层面效率和质量能同时往上走。1.2 “开放”这两个字意味着什么我给这套工作流取名叫open-code-review强调的就是“开放”。技术选型要开放全部使用社区成熟的开源方案不依赖某家商业公司闭源产品规则配置要开放每个规则、每个阈值都明文写在仓库里团队成员随时能看、能讨论、能改流程要开放不搞“某某大佬说了算”的黑盒评审任何人在任何时间都能通过PR评论提出意见。这套理念实际上借鉴了开源社区运作模式。想想Linux内核或React的代码评审流程提交的patch要过CI、过静态检查、过reviewer的逐行阅读所有讨论都公开留痕。开源项目能保持长期高质量靠的不是几个天才而是这套“开放的硬约束”。open-code-review就是要把这种模式移植到普通业务团队不需要每个人都是专家只要流程是公开透明的质量问题就藏不住。1.3 人工审查和自动化检查怎么分工这是搭建open-code-review时最需要想清楚的问题也是踩坑最多的地方。我的判断是这样的能用规则定义清楚的问题交给机器需要上下文理解、架构判断、产品语义分析的问题交给人。举个例子缩进是4个空格还是2个、变量命名用驼峰还是下划线、import语句有没有排序这类纯格式和规范类问题机器应该一秒钟搞定人来看纯粹浪费生命。还有一层是“一眼就能看出但人容易看漏”的问题比如把一个可选链写成了直接属性访问在某个边界场景会抛TypeError这类问题ESLint规则完全可以覆盖。但机器永远替代不了人做这几种判断某个函数是不是被拆得太碎或者太臃肿、这个模块的接口设计面对将来的需求变化够不够灵活、用这个第三方库的代价是不是太大、这段业务逻辑的理解和产品预期是否一致。所以合理的分工是机器做第一道硬性过滤人做第二道深度review。两道关卡都有明确标准和可见的产出物审查这件事才算真正闭环。2. 方案选型构建open-code-review的四个关键决策2.1 规则引擎ESLint作为底座静态检查规则引擎我没有犹豫就选了ESLint。原因不复杂生态最成熟社区规则包几乎覆盖了JavaScript生态里所有好东西TypeScript支持通过typescript-eslint插件也算稳定的第一梯队方案。这几年虽然冒出来Biome、oxlint这类主打性能的新秀速度确实快得离谱但规则生态和自定义能力还是差ESLint一大截在团队内推广时免不了遇到“这条规则我想调一下”的需求ESLint的配置灵活度是无可替代的。ESLint的配置这两年也在经历从.eslintrc到eslint.config.js的迁移新项目建议直接用后者。再说一个我实际对比测试后的结论ESLint虽然全量扫描大项目时速度一般但配合lint-staged做增量扫描后单次执行时间能压到一两秒内用户体验是完全可以接受的。性能问题根本不用提前焦虑。2.2 格式与样式Prettier加Stylelint组合格式问题我强烈建议直接交给Prettier它最核心的价值是让团队不再争论“代码该长什么样”。凡是格式争议一律用prettier来解决不需要人在review里去点评“这里多了个空格”。CSS/SCSS/Less这层样式规则用Stylelint来管它和ESLint的思路很像规则集可以覆盖属性顺序、颜色值是否用变量、选择器嵌套深度这类问题。如果你项目里用CSS Modules或TailwindStylelint同样能适配。这里有个常见误区Prettier只管格式不管代码质量规则。格式统一是Prettier的活样式规范是Stylelint的活两者不冲突也不该互相替代。2.3 CI管道与机器人Reviewdog打通评论闭环工具输出检查结果只是第一步关键是结果怎么呈现给提交者。把几百行日志丢在CI输出里让人自己翻和直接在PR代码行上挂一条精确的评论体验差距是本质性的。我选Reviewdog来做这件事。Reviewdog是一个能把任意linter输出翻译成代码评审评论的开源工具支持GitHub、GitLab、Bitbucket等主流平台。它做的事情可以用一句话概括把ESLint、Stylelint、甚至自定义脚本的输出解析出来然后在对应的PR代码行上添加评论标记error级别的问题时同时让检查任务失败阻止合并。这样审查意见就长在了代码上提交者打开PR一眼就能看到“这个问题出在这一行、是什么问题、怎么改”不需要再到处找日志。2.4 要不要上SonarQubeSonarQube是个好东西覆盖面广能检测重复代码、圈复杂度、潜在bug模式甚至维护一个“技术债”指标。但我给大多数团队的诚恳建议是初期先不要上等基础流程跑顺了再考虑。原因有两个。第一SonarQube是重组件跑起来要一个服务端需要数据库、磁盘存储、持续更新规则库对于中小团队来说是额外运维负担。第二规则多不代表效果好它的海量规则集对老项目来说会产生大量历史告警处理这堆存量告警本身就是一件劝退新人的事。如果项目对代码质量要求很高、团队有专门的工程效能人力SonarQube值得研究否则先用ESLint加Reviewdog把流程跑起来效果立竿见影。下面是我最终采用的工具组合和替代方案汇总工具职责选型理由替代方案ESLintJavaScript/TypeScript静态规则生态最全、自定义能力强Biome、oxlintPrettier代码格式化团队零争议格式化方案内置格式化工具StylelintCSS/SCSS样式规范规则精细、适配主流CSS方案无成熟替代ReviewdogCI评论机器人打通检查结果与PR评论CodeRabbit、GitHub Action botHusky lint-staged本地提交前检查把问题拦在push之前节省CI排队时间pre-commit框架3. 实操过程与核心环节实现3.1 本地环境的规则初始化先说本地这套怎么搭。我建议先用npm初始化项目然后一次性安装全部依赖npm init -y npm install --save-dev eslint eslint/js typescript typescript-eslint prettier stylelint stylelint-config-standard lint-staged husky装完之后创建eslint.config.js。这里给一份兼容TypeScript项目的基础配置可以直接抄import js from eslint/js; import tseslint from typescript-eslint; export default tseslint.config( js.configs.recommended, ...tseslint.configs.recommended, { files: [**/*.{ts,tsx}], rules: { typescript-eslint/no-explicit-any: error, typescript-eslint/no-unused-vars: [error, { argsIgnorePattern: ^_ }] } }, { ignores: [dist/, node_modules/, coverage/] } );这个配置里最关键的一行是no-explicit-any设置成error后禁止任何地方出现裸any类型。项目里总有人图省事写个any让类型检查闭眼这条规则必须卡死不然TypeScript的意义直接腰斩。接下来把Prettier和Stylelint配置也立起来。Prettier可以在package.json里加一个prettier字段也可以单独建.prettierrc文件我习惯单独建文件便于和ESLint的规则统一审查{ semi: true, singleQuote: true, trailingComma: es5, printWidth: 100 }Stylelint的配置比较简单可以直接继承标准集{ extends: stylelint-config-standard, rules: { color-hex-length: long, declaration-empty-line-before: null } }3.2 用lint-staged做提交前增量检查直接全量跑ESLint在团队里不现实项目一大就是几十秒甚至几分钟没人愿意每次提交都等。正确做法是用lint-staged只对暂存区里变动的文件做检查在package.json里加这段配置{ lint-staged: { *.{js,ts,tsx}: [eslint --fix, prettier --write], *.{css,scss}: [stylelint --fix, prettier --write] } }然后执行这行命令初始化Husky钩子npx husky init echo npx lint-staged .husky/pre-commit这样团队每个人在本地提交时变动的文件都会被自动格式化并做静态检查如果有error级别的违规提交会直接被拦截。这一步的意义比CI里的检查更大越早发现问题修复成本越低。试想一个错误如果在本地就拦住了连提交都不会发生更不会出现在PR里被大家围观。3.3 把Reviewdog接进CI本地检查只是第一道关防止的是“自己看不见自己的问题”。CI里的检查才是所有变更的必经之路因为它不受个人本地环境的影响任何绕过本地钩子的操作在里面都会现出原形。我用GitHub Actions来演示在项目里创建.github/workflows/code-review.ymlname: open-code-review on: pull_request: types: [opened, synchronize, reopened] jobs: review: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 20 - run: npm ci - name: Run ESLint with Reviewdog uses: reviewdog/action-eslintv1 with: github_token: ${{ secrets.GITHUB_TOKEN }} reporter: github-pr-review fail_on_error: true eslint_flags: src/**/*.{ts,tsx}这里有两个参数值得细说。reporter: github-pr-review表示Reviewdog会把评论直接以review记录的形式挂在PR上而不是在Actions日志里输出。fail_on_error: true表示只要存在error级别的问题这个Action就返回失败配合GitHub分支保护规则可以做到“有error级别问题就不允许合并”。团队里总有人想钻空子这个硬门禁直接把路堵死了。如果你用的是GitLab流程也类似用reviewdog/action-eslint的GitLab版本或者调用reviewdog CLI配置变量的写法不同但逻辑完全一致。3.4 规则配置的版本化管理这是我踩过很多坑之后特别想强调的一环。规则配置绝不能散落在各个人本地或者只存在于某个CI脚本里必须作为独立文件纳入git版本控制最好是放在一个独立的config仓库或项目根目录的.config/目录下然后通过包管理器或脚本统一引用。我们团队现在的做法是维护一个叫code-review-config的内部npm包里面包含eslint配置、stylelint配置、prettier配置以及对应的依赖版本锁定。各个业务项目通过npm安装这个包然后在自己项目里re-export或者引用。这样调整规则时只需要改动一个仓库发一个新版本各业务项目升级依赖即可生效。这个做法初期有点费事但一旦业务项目超过三五个收益会非常明显。否则你会在每个项目里手工改规则改到第三个的时候就会彻底崩溃而且改完之后各项目行为还不一致所谓“统一标准”就成了一句空话。open-code-review的“open”理念也体现在这里规则不是某个人拍脑袋定的而是全团队能审查、能提议修改的开放资产。4. 常见问题与排查技巧实录4.1 规则误报和噪声太多团队直接当成“狼来了”这是所有引入静态检查的团队遇到最多的第一个问题没有之一。刚开始配置规则太激进把几十个rules全开成error结果就是每个PR几十条评论开发提一次PR被机器喷得体无完肤气得直接摆烂不看评论了。正确做法是分级治理。把规则分成两级error级别只保留会明显影响功能正确性的规则比如no-undef、no-unreachable、no-explicit-anywarn级别用来提示代码风格问题比如prefer-const、no-var。Reviewdog默认对warn只评论不失败这样开发者在PR里看到warn时会知道“这里可以更好”但不至于被卡住。还有一种很有效的技巧新规则的推进要“渐进式”。不要一次性把所有规则都打开而是每一两周引入几条新规则并在PR评论中说明为什么加这条规则、能解决什么具体问题。让团队成员感觉规则的增加是有逻辑的不是被工具绑架。4.2 审查速度越来越慢CI排队时间暴涨项目跑几个月后另一个痛点浮出水面CI上的审查任务越来越慢。原因多半是检查范围失控ESLint和Stylelint把整个项目的文件全扫了一遍包括那些几年没动过的老文件。有两个解法。第一配合之前提到的lint-staged思想在CI里也优先做增量检查只针对本次PR改动的文件跑ESLint全量检查可以放到定时任务里执行。第二给ESLint配置合理的ignore文件把dist、构建产物、第三方目录都排除掉。经过这样调整后我们组CI的审查时间从平均5分钟降到了40秒左右效果非常显著。4.3 老项目历史存量问题太多新规则一开就告警上千引入新规则的时候最容易踩的坑一个运行多年的老项目规则一开ESLint瞬间报出上千个问题一半以上都是历史代码留下的。这时候如果把fail_on_error开成true等于让整个团队在第一个月什么都别干天天改历史债是典型的“好心干坏事”。这类问题我的处理思路是“新旧有别”。在ESLint配置中通过files字段对旧目录做豁免或者用overrides把规则只作用在src/new-feature这类新编写目录上同时建立一条定时任务每周自动扫描一次历史遗留问题的数量指标只要求不增长不要求清零。等技术债被一项项修掉再逐步收窄豁免范围而不是一上来就搞一刀切。这样做的好处是团队可以用很平和的心态持续改进而不是被海量历史告警整到焦虑。4.4 “工具再强也挡不住人不想看”的团队阻力技术方案写得再完善如果团队里有人就是不配合闭环也撑不起来。有段时间我们组有人每到提PR时就想着绕过CI把检查失败强行跳过合并。后来我复盘了一下发现根本原因是他们觉得这套流程是“被管控”而不是“帮他们兜底”。破局的关键是转化视角。我们在周会上不评价“谁的代码又没过检查”而是挑几条典型的Reviewdog评论当众感谢那位写了差错的同事——因为他的代码代表了团队踩过的坑改完之后其他人就不会再犯。这种正向反馈很管用。另外你们可以在CI里加一个“审查记录”展示看板统计每人每月拦截了多少潜在问题用数字告诉团队这套流程不是什么监督工具而是大家的共同防线。4.5 冷启动项目时的最小落地清单如果团队或项目是全新的千万不要一上来就把全套东西铺开。建议按这个顺序小步快跑先上Prettier Husky钩子统一格式立竿见影。第二周加ESLint基础规则开启error硬门禁。第三周接入Reviewdog让检查结果变成PR评论。等到所有人都习惯了上面的流程再考虑SonarQube、圈复杂度、测试覆盖率这类进阶指标。这个节奏的核心思路是每一步带来的摩擦都小于收益让团队成员感觉到流程在帮他们而不是烦他们。工具是用来减少摩擦的不是用来制造恐惧的。根据我个人这段时间的体会open-code-review这套工作流真正跑起来之后团队审查效率肉眼可见地上了一个台阶原来一天最多能认真review的PR数量有限现在机器把简单问题全扫光了人类reviewer只需要专注架构和业务逻辑讨论质量完全不一样。现在我每次打开PR看到机器评论加人类讨论的组合心里会安定很多因为知道这些代码上线之前至少被两双以上不同维度的眼睛认真看过了。
阅读完成 · 觉得有帮助?
咨询建站