前端团队做 Code Review 这件事,喊了很多年,真正落地到每天都严格执行的团队其实很少。我自己带前端组那会儿,最头疼的也不是功能做不完,而是每次拉 Review 都像求人办事——有人不点开看、有人点开说句"LGTM"就完事、有人跟写代码的吵起来。后来我们花了大半个季度把一套 open-code-review 的流程彻底跑起来,才慢慢感觉到:Review 不是形式,是团队工程质量的下限。
这篇内容不是给你讲什么高深理论,而是把我们前端团队从"评审靠自觉"到"评审成制度"的全过程复盘一遍。说说我们怎么定标准、怎么拆检查点、怎么处理评审里的沟通冲突,以及为什么 open-code-review 这种开放式评审机制会比传统"小圈子盯代码"更适合前端团队。不管你是十几人的前端组,还是在创业公司里一个人扛多个项目,这套东西都能直接改一改拿来用。
1. 先正视一个问题:为什么大多数团队的 Review 都在走过场
我见过不少团队把 Code Review 写在开发流程里,Git 平台上也开了 MR/PR 必须有人 approve 才能合并的规则,但实际效果跟没做差不多。评审的人打开 diff 看一眼,没有明显报错、格式没乱,就点个通过;提测之后出了线上 bug,一查全是评审阶段就该发现的问题。问题的根源不在人不自觉,而是大多数团队把 Review 当成了"合并前的一道手续",而不是"质量共建的过程"。
1.1 评审疲劳:把精力消耗在了低频信号上
前端代码的 diff 往往又长又杂。一个功能分支可能同时改了样式、组件、状态管理、接口请求和路由配置。评审人为了跟上节奏,可能得花半小时一小时把上下文全部拉起来。人的注意力是有限的,当你长期在大量琐碎的 diff 里反复看,很快就会产生评审疲劳——看到绿色 diff 就条件反射想通过。这不是责任心的问题,是流程设计的问题。
我们后来做了一件事:把提交粒度拆小,一次 MR 只改一个逻辑主题。比如"新增购物车数量角标"和"重构购物车状态管理"分两个 MR,不混在一个分支里。拆开之后,评审人每次面对的 diff 通常只有几百行,甚至几十行,注意力和判断力就能集中在真正需要看的地方。这个改动立竿见影,评审意见的质量直接提高了一个档次。
1.2 上下文缺失:没有描述和截图,评审全靠猜
另一个痛点就是 MR 描述写得太糊。很多开发提 MR 就写一行"完成购物车功能",里面改了 20 个文件。评审人看到这种描述,根本不知道代码想干什么、为什么这么干、有什么限制条件,只能自己对着代码猜。前端项目里这种问题尤其明显,因为很多东西是视觉层面的——你改了布局、改了间距、改了动效,不看页面根本不知道效果对不对。
应对办法也很直接:强制 MR 模板。修改了什么、为什么这样改、影响范围、自测结果、关键截图,都必须要填。截图这一条对前端特别重要,你贴一张本地跑出来的界面截图,比写二十行说明文字都管用。后面我们还会把 MR 模板的完整示例放出来,可以直接抄。
1.3 面子文化:评审意见没人敢提
这是最微妙的一点。团队里大家天天见面,经常一起吃饭一起加班,到 Review 的时候发现对方的代码有明显问题,多数人第一反应是:"当众提出来会不会不太好?"于是很多问题就在这种"算了算了"的心态里被放过去了。
open-code-review 强调的恰恰是打开这层窗户纸。它把评审从私下里的口头交流变成公开的、格式化的、对事不对人的技术讨论。我们在团队里立了一条规矩:评审里所有问题都记录在 MR 评论区,公开可见,禁止私聊说"你那个代码有问题我帮你改了"之类的话。一旦所有问题都公开化、书面化,大家反而能把焦点放到代码本身,而不觉得是人身攻击。这个转变花了我们大概一两个月,但效果是长期性的。
2. Review 前的工作:把"提 PR"当成一次正式汇报
很多人觉得评审是评审人的事,提交方只要把代码写出来就行。这其实是最根本的误解。评审质量的上限,在代码提交的那一刻就已经定了。提交方有没有认真自测、描述写得清不清楚、变更范有没有收敛——这些决定了评审人能发挥多少价值。所以 open-code-review 流程的第一步,不是列评审点,而是训练所有人把 MR 当成一份要拿去汇报的材料来准备。
2.1 MR 描述模板:描述里写什么,评审者才不用猜
我们团队内部沉淀了一套 MR 描述模板,固定了几大块内容,大家提合并请求的时候照着填。这里直接分享一下:
## 背景与目标 (说明这个变更要解决什么业务问题或技术问题) ## 变更内容 (列出主要的改动模块和文件,说明每个点改了什么) ## 设计思路与取舍 (为什么要采用这个实现方式,有哪些替代方案被放弃及原因) ## 影响范围 (会影响哪些页面/组件/接口,是否需要后端或其他团队配合) ## 自测记录 - 正常路径:xxx - 边界条件:xxx - 浏览器兼容:Chrome/Safari/Firefox 实测结果 ## 截图 (前端项目必填:核心界面前后对比) ## 其他说明 (如依赖项升级、潜在性能考量)一开始推行这个模板的时候,组里有人嫌麻烦,说"又不是写论文"。但坚持了一两个月之后,大家逐渐感受到了好处:评审人上手快,沟通成本低,以前需要来回追问的事情在描述里都写了。慢慢地,"把 MR 写清楚"反而变成了一种技术表达能力的体现。
2.2 提交前自测清单:先在本地把低级问题干掉
每个人提交前应该按下面的清单过一遍,否则 MR 提出来就是在浪费评审人的时间:
- 功能路径是否完整跑通,而不是只看自己改的那一行
- 关键交互在窄屏、宽屏下的表现是否检查过
- 有无明显 console 报错,有无未处理的异常
- 用到的接口数据字段是否和后端实际返回一致
- 改动是否引入了多余依赖或无用代码
- TS 类型是否通过检查,有没有大量使用 any 代替
自测这个环节,本质上是把"人工评审"的时间留给真正需要思考的问题——比如状态设计是否合理、性能是否达标、边界是否处理。如果低级问题一堆就拉评审,评审人大多会直接失去耐心,后面的有效意见也没了。
2.3 小步提交:一次 MR 只做一件事
前面提过拆小 MR,这里补充一下实操细节。我们内部要求单个 MR 的改动文件尽量控制在 20 个以内、核心逻辑改动控制在 500 行左右。一旦超过这个量级,评审人会本能地产生抗拒心理,更倾向于"扫一眼就通过"。
拆 MR 跟写代码一样需要能力。你得规划好分支策略:功能 A 的分支和功能 B 的分支相互独立,先后合并,而不是在同一分支里堆叠。这里可以简单列一下我们现在用的方式:
# 基于 main 拉一个功能分支 git checkout -b feature/cart-badge # 完成第一个逻辑主题后提交,推送到远端 git push -u origin feature/cart-badge # 合并后再基于最新 main 拉下一个功能分支 git checkout main git pull git checkout -b feature/cart-state-refactor这套做法起初会让人觉得分支变多、流程变重。但两个 MR 各 200 行,比一个 MR 400 行,评审质量和速度完全不一样。从时间成本看,前者是省时间的。
3. 前端评审的核心检查面:拿到一个 MR 到底该看什么
这一节是全文最核心的部分。很多人评审前端代码,只能看出"语法对不对""变量命名好不好",但前端项目的坑往往不在语法层面,而在数据流、状态设计和交互细节里。open-code-review 的流程之所以有效,是因为我们把评审做成了分层的检查——每一层都有明确的问题清单,评审人按清单过一遍就能覆盖大多数风险点。
3.1 第一层:变更意图与 diff 范围
拿到 MR,先别急着看代码细节,先看整体 diff 是否和提交者描述的目标一致。常见的问题是:为了改一个按钮颜色,把整个组件的逻辑也重构了。这种顺手改其实很危险——你以为的无意改动,可能把另一个功能的预期破坏了。这一层的检查点包括:
- 变更是否局限于 MR 描述提到的模块
- 是否有无关文件被改动(比如自动生成的配置文件、锁文件)
- 是否有删除的代码是被其他模块引用的
被乱改的锁文件这个坑,我们踩过好几次。有人本地用不同版本的包管理器跑了一下,锁文件就被大幅改动,混进 MR 里一起提上来,评审人一旦没注意,合并后队友拉代码就可能装出不同的依赖树,线上跑出诡异问题。所以现在我们对锁文件的改动非常敏感。
3.2 第二层:状态管理与组件数据流
前端项目里最容易出潜在 bug 的就是状态滥用。评审时要追问几个问题:
- 这份数据真的需要放进全局状态吗,还是组件本地 state 就够
- 同一个领域的数据有没有出现在多个 store 里,互相需要同步
- 组件从 props 拿数据和从 store 拿数据,来源是否统一
- 有没有直接修改 props 或外部 store 的操作
我见过太多代码,把接口返回的数据塞进全局 store,组件里又通过 computed 派生一堆字段,到最后改一个字段牵一发动全身,没人说得清数据流是怎样的。评审时不能只关注这段代码能不能跑,还要追问这个状态设计未来扩展性怎样。如果发现状态放错了层级、派生逻辑绕来绕去,哪怕功能正常也应该提出来——这种问题迟早变成技术债,而且越晚处理越贵。
3.3 第三层:UI 实现与样式正确性
前端不同于后端的很重要一点,是代码逻辑过了,视觉效果不一定对。样式相关的检查不能靠读代码,必须看截图、看实测。我们整理了一份样式检查清单:
- 响应式断点是否正确,窄屏下有没有横向溢出
- 组件在相邻容器内的间距是否符合设计规范,而不是硬编码魔数
- 字体、字号、行高是否继承全局设计 token,而不是自定义了一套
- 交互状态(hover/focus/disabled/loading)是否都实现完整
- 是否有深色模式适配问题(如果项目支持)
样式检查最容易被忽略的是 focus 态。很多开发者做完点击、hover,却漏掉键盘操作时的焦点样式,这在无障碍检测里是硬伤。评审时如果看到按钮、输入框没有对应的 focus 样式,可以直接提出补上。
3.4 第四层:性能与渲染开销
前端的性能问题在评审阶段就需要预判。常见的高频问题:
- 大列表渲染是否使用了虚拟滚动,还是直接把几千条数据全量渲染
- 组件更新频率是否过高,有没有不必要的重渲染
- 事件监听和定时器是否在销毁时清理
- 图片资源是否做了懒加载,体积有没有压缩
- 依赖项的体积、构建产物的大小是否有明显增长
评审时注意一个点:初级前端容易追求"把所有东西都做成响应式",结果一个数组变化就触发大范围重渲染。评审时可以打开 Performance 面板粗略看一眼渲染耗时,如果页面明显卡顿,那不管代码逻辑多优雅都要打回优化。
3.5 第五层:类型与边界条件
TypeScript 已经是前端标配了,但 TS 的类型检查通过不代表逻辑边界处理正确。评审时要专门看看这些分支:
- 接口返回的数据是否为空的处理
- 嵌套对象是否存在空指针风险
- 列表边界(空数组、长度为 1、数据量超级大)表现是否正常
- 用户快速重复操作时有没有防重复提交
- 时间、金额等格式化边界(时区、精度、负数)
type 层面还有一个高频痛点:滥用 any。很多项目里接口响应类型没定义完整,前端拿过来直接 any 掉,等线上报错才到处找字段来源。评审时应定一条硬规则:代码里不允许出现显式 any,除非有注释说明理由并经过评审确认。
3.6 第六层:安全与敏感信息
前端的安全责任常被低估。评审时重点关注下面几个方向:
- 代码中是否有明文硬编码的密钥、token、账号信息
- 用户输入是否经过转义,是否存在 XSS 注入风险(特别是 v-html 之类的动态渲染)
- URL 拼接是否使用编码处理
- 权限控制是否只依赖前端路由守卫,还是后端同样校验
前端权限校验只是体验层的分流,不是真正的安全边界。评审时如果发现某个接口的敏感操作只在前端做了菜单隐藏,后端没有鉴权,必须提出增加后端校验。
下表汇总了上面六个层面和它们的检查重点,评审人可以直接打印出来当指引:
| 检查层级 | 核心关注点 | 常见脏点 |
|---|---|---|
| 变更意图 | 改了什么、是否超出范围、是否有误改动 | 无关文件、锁文件混乱 |
| 状态数据流 | 全局/局部状态分层、数据来源统一 | 滥用 store、重复状态源 |
| UI 与样式 | 响应式、设计 token、交互态、无障碍 | 硬编码样式、缺 focus 态 |
| 性能渲染 | 渲染成本、事件清理、资源体积 | 大列表全量渲染、内存泄漏 |
| 类型边界 | 空值、边界值、重复操作、格式化 | 滥用 any、空指针、未防重复提交 |
| 安全合规 | 敏感信息、XSS、鉴权边界 | 硬编码密钥、前端权限当安全 |
4. 评审反馈的硬规则:怎么提意见才能不伤和气、不流于形式
评审意见能不能被接受、能不能转化为有效动作,很大程度上取决于意见的表达方式。前端团队的沟通通常比较直接,但代码评审这件事还是得讲究方法。粗暴地甩一句"这段代码写得有问题",等于没说;温和到不敢提,等于白审。我们在实践中沉淀了几条反馈硬规则。
4.1 反馈分级:什么必须改、什么可以后续、什么只是疑问
每条评论都应该有一个明确的"级别前缀",没有级别的评论是无效评论。我们内部约定三级:
- P0(阻断):功能错误、安全漏洞、明显性能问题,必须修改后重新评审才能合并
- P1(建议):当前不修改也可以用,但后续大概率引发问题,最好在本次修掉
- P2(可选):风格、命名、后续优化方向的建议,可以记录为后续 TODO
用分级的好处是,提交方能快速判断每个意见的处理优先级,评审双方不再为"这算不算问题"争论半天。实践下来,P2 占多数的时候评审氛围普遍更轻松,因为大家知道 P2 不是强制修改的要求,用不着防御心理拉满。
4.2 用提问的方式给反馈,而不是下命令
给意见时建议用"为什么/是否/有没有考虑"的句式,而不是"你应该/你必须/写成这样不行"的句式。比如:
- 命令式反馈:这个组件不应该直接改 store 里的数据。
- 提问式反馈:这里直接改 store 里的共享状态,会不会影响其他页面的展示?要不要考虑用事件触发父组件更新?
两种表达想要传达的内容可能一样,但接收方的感受完全不同。提问式把决策权留给提交方,让对方自己去推导问题的存在性。一旦他自己得出了"应该改"的结论,这次讨论就变成了主动的技术成长,而不是被强迫改代码。
4.3 评审人不应该帮忙改代码,更不建议直接合并代修
这一点每个团队都容易踩。看到别人的代码有个小瑕疵,评审者顺手改了,然后说"我帮你修了一下"。这种操作看着是热心,实际坏了规矩:代码库里出现了评审者写但提交者不完全理解的代码,出了问题责任说不清。正确的做法是只描述问题和建议方案,由提交方自己修改。
我们也遇到过评审来回好几轮、改动量已经很大、双方都有点焦虑的情况。这时候可以用一套固定的动作收尾:提交方把历史评论逐条回复,标记每个 P0/P1 的处理结果,评审者逐条确认或继续追问。把节奏掌握在"逐条确认"这个动作上,能避免讨论发散、跑偏。
4.4 合并条件要明确:谁有权合并,什么状态才能合并
open-code-review 流程里必须明确合并权限。建议至少一个核心评审者 approve 且无未处理的 P0 级评论时,才能合并。关键或公共模块(比如公共组件库、构建配置、权限逻辑)可以设置 Code Owner,改动这些文件必须由指定 Owner 审批。千万不要让开发者自己 approve 自己提交的 MR,这条底线从流程开始就要守住。
5. 落地一套 open-code-review 流程:具体到每个环节怎么做
前面讲的都是原则和检查点,这节我们直接把整个流程串起来,从提交前到合并后,每个环节给出能照做的动作定义和时间上限。这套东西在不同团队规模下都需要裁剪,但骨架是通用的。
5.1 流程全景:从编码到上线的七个动作
| 环节 | 动作 | 负责角色 | 时间要求 |
|---|---|---|---|
| 提交前 | 本地自测 + lint + 类型检查通过 | 提交者 | 无 |
| 提交流程 | 按 MR 模板填写描述,附截图和自测记录 | 提交者 | 无 |
| 自动检查 | CI 运行单测、构建、代码扫描 | 机器 | 10 分钟内完成 |
| 人工评审 | 按六层检查点给出分级意见 | 评审者 | 24 小时内给出首轮意见 |
| 修改反馈 | 提交者逐条回复,修复 P0/P1 | 提交者 | 48 小时内完成 |
| 复核确认 | 评审者逐条确认,必要时循环 | 评审者 | 24 小时 |
| 合并上线 | Code Owner 或有权限的评审者执行合并 | 指定人 | 确认后立即执行 |
时间要求可以根据团队节奏调整,但不能不设限。没有时限必然拖成"评审马拉松",大家都没效率。我们的经验是:超过 3 天没完成一轮完整评审的 MR,建议提交者直接在群里提醒评审人,超过一周没动静的,找 leader 介入。
5.2 自动化闸门:让机器先把低级问题挡在外面
人工评审资源有限,得用在刀刃上。提交前置钩子和 CI 阶段能自动检查的事,绝不让评审人看。在项目仓库里加一套 pre-commit/pre-push 钩子,配合 lint-staged 做增量检查,基础配置长这样:
{ "husky": { "hooks": { "pre-commit": "lint-staged", "pre-push": "npm run lint && npm run type-check && npm run test" } }, "lint-staged": { "src/**/*.{ts,tsx,vue}": ["eslint --fix", "prettier --write"] } }CI 阶段在跑完 lint 和单测之后,再加一轮构建产物大小对比,把新增依赖导致体积暴涨的 MR 直接拦下来。这样评审者拿到手里的 MR,基本没有低级格式问题,他们集中精力看逻辑和数据流即可。
5.3 评审人分配:怎么保证每一行代码都有人看
小团队里,天然就是互相评审,不太需要复杂的分配机制。但超过十个人的组,就要考虑评审人的负载均衡。我们用的方式很简单:
- 按照业务模块指定 primary reviewer 和 backup reviewer
- primary reviewer 休假定事,backup 顶上
- 每个 MR 至少找一名对这块代码不熟悉的人参与评审,也就是"新鲜视角"
"新鲜视角"这条我们坚持了很久。只让同一批老手互相评审,容易形成思维定势——大家共享同一套盲区。拉一个不熟悉该模块的人进来,反而能问出很多"为什么这样写"的基础性问题,逼着提交者重新审视设计。
6. 评审标准的版本化:让团队的共识持续生长
Review 流程跑起来之后,还有一个关键环节:把评审中发现的高频问题沉淀成标准,并且让标准跟着项目一起演进。很多团队的 Review 之所以越做越形式化,是因为检查项永远停留在团队建立初期的几条,项目在变、技术栈在变、团队在变,标准却原地不动。
6.1 建立前端 Review 高频问题库
每个季度我们都会把评审评论全部过一遍,挑出出现频率最高的 10~20 个问题,整理成一份《前端常见评审问题清单》。这份清单不是摆设,而是新人的参考手册,也是评审人自查的备忘录。举几个我们进入清单的例子:
- 组件卸载后异步回调仍更新状态,导致内存泄漏
- 列表 key 使用 index 导致渲染错乱
- 接口请求无错误处理,loading 状态永远不关闭
- 只考虑了成功路径,没有设计空态、失败态、超时态
- 深层对象更新用扩展运算符导致层级丢失
- 事件节流或防抖缺失,滚动或输入时频繁触发计算
这些问题单个看似乎都不大,但每个都造成过线上事故或用户投诉。把高频问题库建立起来之后,评审人不用每次重新思考,"对应清单检查"即可。新人写代码前先看一遍清单,也能提前规避大部分常见坑。
6.2 让标准可讨论、可更新,而不是永不变化
清单要定期修订。我们每个季度安排一次技术评审复盘会,大家把最近遇到的典型问题、新工具链带来的新检查点、团队风格调整等因素加进清单。比如我们引入 React 18 和并发渲染相关 API 之后,就专门加了一条"并发模式下是否有重复调用风险"的检查项。再比如引入设计系统之后,加了"是否符合组件库 api 设计规范"的检查项。这个过程让评审这件事持续保持新鲜感,不会变成每年一样的八股文。
如果团队里有人对某条评审标准有不同看法,应该直接提出来,在公开讨论中达成新的共识。评审标准不是某个 leader 一锤定音,而是团队共同维护的文档。这条听起来简单,却是让评审真正融进团队文化的关键。
6.3 数值指标:评审这件事本身也需要被量化
没有度量就很难改进。在每个迭代结束后,我们会拉一组简单粗暴的指标:
- 平均首轮评审时长:反映评审响应是否及时
- 评审驳回率(有 P0/P1 意见的 MR 占比):反映提交质量
- 人均评审评论数:反映评审参与度
- 每周期评审引发的 bug 数:反映评审机制的兜底效果
这些指标不是用来考核某个人,而是用来监控整个流程是否健康。比如评审驳回率突然下降,可能是提交质量真的变好了,也可能是评审变松了。需要结合评论内容和时长综合分析,不能只看一个数字。
7. 写在最后:Review 文化不是一堆规则,而是团队对代码的尊重
这套 open-code-review 流程在我们团队落地半年之后,最直观的变化不是 bug 变少——bug 确实变少了,但更明显的是大家对"提交代码"这个动作的态度变了:会认真写描述、会提前自测、会在提交前用别人的视角审视一遍自己的代码。代码不像从前那样只是"写完"就急着推上去,而是多了一层"我要把它展示给同事,至少要经得起看"的自觉。
如果你也想在团队里推行类似的东西,我的建议是从小的切口开始:先定 MR 模板和 P0/P1/P2 分级,这两条立竿见影。等大家习惯了再逐渐加检查点、铺自动化、做复盘。不用一次把整个体系硬塞到团队里,否则只会遇到反弹。
最后再分享一个小经验:Review 里发现的问题,哪怕是 P2,也不要只丢一句话就完。尽量写清楚问题发生的场景、可能的触发路径和参考的改进方向。一条好的评审意见,本质上是给同伴的一份技术指导——认真写评论的团队,代码水平和协作氛围都会肉眼可见地向好。