Code Review实战:提升代码质量与团队协作效率的关键技巧
项目初期的技术选型
上个月收尾的这个后台管理系统,前端团队就三个人,工期压得紧,需求又变来变去。一开始我们连要不要做 Code Review 都犹豫过——毕竟人少、节奏快,怕拖进度。但之前另一个项目因为没 review,上线后发现好几个低级 bug,比如把 user.id 写成 user.Id,还有个循环里直接 setState 导致页面卡死。所以这次咬牙决定:哪怕再赶,也得走 review。
工具选了 GitHub 的 PR(Pull Request)流程,没上 Gerrit 或其他重型系统,主要是团队都熟悉 GitHub,上手快。规则也定得简单:所有功能分支必须经过至少一人 review 才能合入 main;紧急 hotfix 也得事后补 review。说实话,开始执行时有点别扭,尤其我这种老写单干代码的人,总觉得“别人看我代码是不是在挑刺”。但几轮下来发现,其实大家更关注逻辑是否清晰、边界有没有覆盖,而不是抠格式。
最大的坑:性能问题
真正让我头疼的是一个数据表格组件的 review。同事提了个 PR,用 map 渲染上千条数据,本地跑着没问题,但测试环境一加载就卡顿。我第一反应是“分页啊”,结果他说产品要求一次性展示全部数据(别问,问就是“客户坚持”)。review 时我指出了性能隐患,但他觉得“先跑起来再说”。结果上线预发环境后,低端安卓机直接白屏。
折腾了半天发现,问题出在两个地方:一是每次状态更新都触发全量 re-render,二是每个单元格都绑了独立的事件处理器。后来我们重写了组件,用了虚拟滚动 + 事件委托。核心改动就这几行:
// 虚拟滚动容器
const VirtualList = ({ items, itemHeight, containerHeight }) => {
const [scrollTop, setScrollTop] = useState(0);
const visibleCount = Math.ceil(containerHeight / itemHeight);
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = startIndex + visibleCount;
const visibleItems = items.slice(startIndex, endIndex);
return (
<div
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={(e) => setScrollTop(e.target.scrollTop)}
>
<div style={{ height: items.length * itemHeight }}>
{visibleItems.map((item, index) => (
<div
key={startIndex + index}
style={{
position: 'absolute',
top: (startIndex + index) * itemHeight,
height: itemHeight,
width: '100%',
}}
>
{/* 渲染单个 item */}
<DataCell data={item} />
</div>
))}
</div>
</div>
);
};
这里注意我踩过好几次坑:key 必须用真实索引(startIndex + index),不能只用 index,否则滚动时复用错乱;外层容器高度必须固定,不然 scrollTop 计算不准。另外,事件委托我们统一挂到父容器上,避免每个 cell 绑函数:
// DataCell 内部不再绑定 onClick
// 而是在 VirtualList 的父容器处理
<div onClick={(e) => handleCellClick(e)}>
{/* ... */}
</div>
// handleCellClick 通过 dataset 获取行信息
const handleCellClick = (e) => {
const rowId = e.target.closest('[data-row-id]').dataset.rowId;
// 处理点击
};
核心代码就这几行
除了性能,另一个高频问题是 API 错误处理。早期代码里经常看到这样的写法:
// 千万别这么写!
fetch('/api/data')
.then(res => res.json())
.then(data => setData(data));
没 catch,没 loading 状态,网络失败直接静默。Review 时我强制要求所有异步操作必须包含错误兜底和用户反馈。后来我们封装了个简单的 hook:
// useApi.js
import { useState, useCallback } from 'react';
export const useApi = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const request = useCallback(async (options = {}) => {
setLoading(true);
setError(null);
try {
const res = await fetch(url, options);
if (!res.ok) throw new Error(HTTP ${res.status});
const json = await res.json();
setData(json);
return json;
} catch (err) {
setError(err.message);
console.error('API error:', err);
// 实际项目中这里会调用 message.error() 之类
} finally {
setLoading(false);
}
}, [url]);
return { data, loading, error, request };
};
用起来很简单:
// 在组件里
const { data, loading, error, request } = useApi('https://jztheme.com/api/users');
useEffect(() => {
request();
}, [request]);
亲测有效,至少不会再出现“点按钮没反应”的用户投诉了。不过这个 hook 没处理重复请求取消(比如快速切换 tab),算是个小瑕疵,但目前业务场景影响不大,就没动。
踩坑提醒:这三点一定注意
- 别让 review 变成格式战争:我们早期花太多时间争论缩进用 2 还是 4 个空格。后来直接配了 Prettier + ESLint,PR 里只讨论逻辑,格式问题交给 CI 自动 fix。
- 新人 PR 别只说“LGTM”:有次实习生写了段防抖,但 delay 写死了 300ms,而搜索接口其实需要 500ms 才稳定。我直接 comment:“这里 delay 建议抽成常量,方便后续调整”,并附上文档链接。比单纯 approve 有用多了。
- 大 PR 拆小:超过 500 行的 PR 基本没人细看。后来规定:功能拆成“骨架 → 逻辑 → 样式”三个 PR,review 效率高很多。
回顾与反思
搞了两个月 Code Review,整体效果不错:线上 bug 少了大概 40%,尤其是低级错误基本绝迹。团队沟通也变多了,以前各写各的,现在会主动问“你这块怎么设计的”。不过也有做得不好的地方:比如有些边缘 case 的测试覆盖还是靠自觉,没有强制要求单元测试;还有一次因为 review 太慢,阻塞了发布,后来我们加了 SLA——普通 PR 24 小时内必须响应。
方案肯定不是最优的,比如没上自动化测试集成,也没做权限分级(现在所有人能 approve 所有 PR)。但对我们这种小团队来说,简单够用就行。毕竟工期紧的时候,能快速发现问题比追求完美流程更重要。
以上是我踩坑后的总结,希望对你有帮助。如果你们有更好的 review 实践,比如怎么平衡速度和质量,欢迎评论区交流!
