上周帮同事看一个 Python 脚本,运行时偶尔卡住,日志只显示 TimeoutError: Operation timed out,但超时设置明明是 30 秒,实际才跑 2 秒就挂了。最后翻到 requests 库的源码里,发现底层 urllib3 的连接池在复用连接时没正确处理 socket 关闭状态,导致线程一直阻塞在 sock.recv() —— 这种问题,光看调用栈根本找不到根因。
别急着改代码,先搞清它到底在干啥
很多人一遇到 bug 就开编辑器删逻辑、加 print、疯狂重启服务。其实最省时间的办法,是打开源码,顺着调用链往里钻。比如你用的是 Vue,页面某个响应式数据不更新,与其反复检查自己的 data 写法,不如直接去 node_modules/vue/dist/vue.runtime.esm-bundler.js 里搜 triggerRef 或 queueJob,看看触发更新的时机和条件到底卡在哪一步。
几个快速上手的小技巧
• 在 IDE 里按住 Ctrl(Mac 是 Cmd)点函数名,能跳转到定义——哪怕它是第三方库里的方法;
• 遇到报错,先复制异常最后一行的类名或方法名,去 GitHub 上搜对应仓库的 Issues,常有现成答案;
• 不确定某段逻辑怎么走的?临时在关键位置加个 console.log(new Error().stack)(JS)或 traceback.print_stack()(Python),比打断点还快。
举个真实例子:React useEffect 依赖数组失效
有次写了个组件,useEffect 里监听 user.id,但用户登录后 id 变了,effect 却没重新执行。查了半天,发现是父组件传来的 user 对象每次都是新引用,但 user.id 没被解构出来——于是翻 React 源码,在 react-reconciler/src/ReactFiberHooks.js 里找到 areHookInputsEqual 方法:
function areHookInputsEqual(nextDeps, prevDeps) {
if (prevDeps === null) {
return false;
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (Object.is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}原来它用的是 Object.is 做浅比较。那问题就清楚了:不是 React 有 bug,是你传进来的依赖项本身不稳定。
别迷信文档,代码才是唯一真相
文档会过时,API 会变更,但正在跑的代码不会说谎。有一次调试 Node.js 的 fs.readFile,文档说默认编码是 utf8,可实际读出来全是乱码。扒到 lib/internal/fs/read_file_context.js,才发现当传入 undefined 作为 encoding 时,内部会 fallback 到 null,进而触发 buffer 模式——而我们恰好漏传了参数。
下次再看到“这个库应该支持 XXX 功能”,别光查文档,打开 node_modules,搜关键词,花三分钟看一眼实现,往往比查十篇博客更快。