读陌生代码:由外向内的方法
有经验的工程师不会像读散文那样从头到尾读代码,而是由外向内读。AI 代码解释器也是在你用同样方式驱动它时最有用。
先看边界:什么进,什么出。找到入口(导出的函数、路由处理器、主循环)和出口(返回值、抛出的错误、对数据库或文件的写入)。在问某一行做什么之前,先让工具指出输入、输出和副作用——这一个问题就能画出地形图。
第二遍:让一个具体的值穿过整段代码。挑一个现实的输入(比如 orders 数组为空的 user 对象),让解释器逐行追踪它。追踪具体值会暴露那些泛泛摘要一笔带过的分支、变量修改和提前返回。
第三遍:给惯用法命名。大多数代码 80% 是模式,20% 是决策。防抖包装、指数退避重试循环、reducer——一旦模式被点名,只有偏离模式的地方才需要真正的注意力。直接问:这里哪些是标准模式,哪里偏离了模式?
然后才逐行读,而且只读经过前三遍后仍然真正不清楚的部分。一个 200 行的文件,通常只会剩下 20 行左右值得细看。
AI 代码解释器会错在哪里
AI 解释的错误是有模式的,而且这些模式可预测到足以逐项排查。
幻觉 API 是经典失败:解释描述了一个不存在的参数、另一个库里名字相似的方法,或者另一个大版本的行为。模型会把训练中见过的几百个库混在一起,所以对 lodash 调用的解释可能悄悄描述的是 Underscore 的行为,对 pandas 代码的解释可能引用两个版本前就被移除的参数。
漏掉副作用更隐蔽。解释器总结了函数计算什么,却漏掉它还会修改传入的参数、写缓存、把个人信息写进日志、触发埋点事件。摘要天然偏向返回值,而现实中的 bug 住在副作用里。一定要单独问一句:这段代码在自身作用域之外改变了哪些状态?
过时惯用法的问题是双向的。模型有时把完全现代的代码说成"过时",有时又把已废弃的模式(var、componentWillMount、Python 2 的除法语义)当作正常写法,因为两个时代的代码都在训练数据里。对任何"当前最佳实践"的断言,默认当它已经过期。
最后是信誓旦旦的复杂度断言:"这段代码是 O(n log n)"正是模型会流畅且错误地生成的那类句子,尤其当 sort、includes、展开运算符这些库调用里藏着循环时。如果复杂度真的重要,自己推导,或者让模型在一个具体的输入规模上数操作次数,而不是直接背公式。
让解释深度恰到好处的提示词
"解释一下这段代码"只会得到一段把代码翻译成自然语言的文字——技术上正确,几乎没用。解决办法是声明你已经知道什么,以及这个解释要支撑什么决定。
如果你是初学者,要求比喻加追踪:"用一个现实世界的比喻解释这段代码做什么,然后把一个示例输入一步步走一遍。"没有追踪的比喻只给模糊的安心感;没有比喻的追踪只是一堵状态变化之墙。
如果你懂这门语言但不熟这个代码库,直接跳过语法:"假设我精通 JavaScript,只解释意图、不显然的设计决定,以及会让维护者惊讶的地方。"这是准备代码评审时杠杆率最高的一条提示词。
如果你在调试,干脆不要要解释,要一场反驳:"作者相信这个函数返回排序后的副本;请引用具体行号,论证支持和反对这个信念的理由。"把模型摆在批评者而非解说员的位置上,它找问题的能力明显更强。
这个工具的难度选择器会替你写好这些框架;编辑生成的提示词,把笼统的水平换成你真实的语言经验。
Instead of:
"Explain this code."
Try:
"I know Python well but have never used asyncio.
1. What is the intent of this function in one sentence?
2. Trace the input [3, 1, 2] through it line by line.
3. List every side effect (I/O, mutation, global state).
4. What would surprise a maintainer? What could break
under concurrency?
Do not explain basic syntax."实例对比:复述式解释与有用的解释
来看一个五行的函数,大多数解释器都会用同样肤浅的方式描述它。下面的代码按邮箱去重用户,保留最后一次出现的记录。
复述式解释会说:"这个函数遍历 users 数组,构建一个以邮箱为键的 Map,然后把 Map 的值作为数组返回。"每个字都对,但毫无用处——这只是把代码朗读了一遍。
有用的解释回答代码引出的问题。为什么用 Map 而不是 Set 或普通对象?(Map 保留插入顺序、允许任意键类型;这里顺序正是重点。)为什么最后一个重复项获胜?(Map.set 会覆盖,后面的条目替换前面的——如果数组按从旧到新排序,这段代码会悄悄保留最新记录,这可能是意图,也可能不是。)锋利的边缘在哪?(只有大小写不同的邮箱被当作两个人;undefined 邮箱全被塌缩进一个桶;返回的数组是新的,但里面的 user 对象是共享引用,改它们会影响调用方。)
最后那段才是你该向任何解释器索要的东西:不是代码做了什么,而是它决定了什么、假设了什么、会在哪里伤到你。如果一份解释里没有一句以"注意"开头的话,就再问一次。
function dedupeUsers(users) {
const byEmail = new Map();
for (const u of users) byEmail.set(u.email, u);
return [...byEmail.values()];
}
Shallow: "Builds a Map keyed by email and returns its values."
Useful: "Keeps the LAST user per email (Map.set overwrites).
Case-sensitive: [email protected] and [email protected] stay separate.
Returned array is new, but user objects are shared
references — mutating them affects the original."两分钟核查流程
把每一份 AI 解释都当作一位自信实习生的草稿:通常是对的,偶尔在要命的地方出错,而且永远意识不到两者的区别。
被点名的 API 要对照真实文档核查——而不是对照模型的记忆。如果解释的成立依赖某个具体函数的行为(不带基数的 parseInt、原地排序的 Array.sort、某个库的默认值),查文档只要三十秒,却能抓住危害最大的错误。
把追踪跑一遍。如果解释器演示了某个输入穿过代码的过程,就在 REPL 或临时测试里真的执行这个输入并对比。叙述出的轨迹与真实输出不一致,是解释有误的最强信号。
对高风险断言交叉盘问。凡是涉及并发、变量修改、错误处理或性能的内容,换个措辞在新会话里再问一遍。一致的回答只是正确性的弱证据;不一致的回答则是强证据,说明你看到的是猜测。
这些都花不了多少功夫——而另一种选择,即只凭一段未经验证的转述去理解并上线代码,正是隐蔽生产事故的诞生方式。