偶现 bug 排查思路
场景
QA 或用户反馈一个 bug,但你在本地无法复现,只是偶尔出现。怎么排查?
分析思路
排查框架
信息收集清单
| 维度 | 要收集的信息 |
|---|---|
| 基本 | 是什么问题?出现频率?操作步骤? |
| 环境 | 设备型号、操作系统、浏览器版本、网络环境 |
| 时间 | 什么时候出现的?每次出现的时间点有规律吗? |
| 用户特征 | 所有用户还是特定用户?新/老用户?地区? |
| 上下文 | 之前做了什么操作?页面停留了多久? |
常见偶现原因
1. 竞态条件(Race Condition)
❌ 接口返回顺序不确定
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<Item[]>([]);
useEffect(() => {
// 快速输入时,后发的请求可能先返回
fetch(`/api/search?q=${query}`)
.then((r) => r.json())
.then(setResults); // 旧请求覆盖新结果!
}, [query]);
}
✅ AbortController 取消旧请求
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<Item[]>([]);
useEffect(() => {
const controller = new AbortController();
fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then((r) => r.json())
.then(setResults)
.catch((e) => {
if (e.name !== 'AbortError') throw e;
});
return () => controller.abort(); // 取消上一次请求
}, [query]);
}
2. 定时器 / 状态清理不彻底
组件卸载后异步操作才完成,更新了已卸载组件的状态。
3. 环境差异
- 不同浏览器行为不同(日期格式、CSS 兼容性)
- 不同时区导致时间计算错误
- 移动端和桌面端事件模型差异
4. 数据边界
接口返回的数据中偶尔出现 null / undefined / 空数组,代码未做防御。
✅ 防御性编程
// 安全访问
const name = user?.profile?.name ?? '未知用户';
const firstItem = list?.[0] ?? defaultItem;
排查工具
| 工具 | 用途 |
|---|---|
| Sentry | 错误日志 + 面包屑 + 设备信息 |
| LogRocket / rrweb | 用户操作录屏回放 |
| 自定义埋点 | 关键操作路径记录 |
| console.trace() | 追踪函数调用链 |
关键路径加日志
function checkout(order: Order) {
console.log('[Checkout] Start', { orderId: order.id, items: order.items.length });
try {
validateOrder(order);
console.log('[Checkout] Validation passed');
submitOrder(order);
console.log('[Checkout] Submitted');
} catch (error) {
console.error('[Checkout] Failed', { error, order });
reportError(error as Error);
}
}
常见面试问题
Q1: 偶现 bug 你一般怎么排查?
答案:
- 收集信息:和反馈者确认环境、操作步骤、出现频率
- 分析可能原因:竞态条件?环境差异?数据边界?
- 尝试复现:模拟环境(浏览器、网络、数据)
- 加日志/埋点:如果无法复现,在关键路径加日志,等下次触发
- 录屏回放:接入 LogRocket / rrweb 等工具,还原用户操作
- Code Review:review 相关代码,寻找潜在问题