让 Claude Code 解释它自己写的代码:防止黑盒
出海web开发有种情况很普遍:它写了一段代码,能跑,你大概知道它在做什么,然后你 commit 了。没问,没让它解释,因为感觉差不多懂了。
三个月后那段代码出了问题。你打开文件,发现你根本不确定某个判断逻辑为什么这么写,也不知道如果改这里会不会影响其他地方。
然后你花了两小时排查,最后发现问题出在一个你当时没仔细看的边界处理上。
那两小时,本来可以在写代码的时候用五分钟换掉的。
「能跑」和「你真的理解」之间,有一条真实的鸿沟
Section titled “「能跑」和「你真的理解」之间,有一条真实的鸿沟”用 Claude Code 开发有一个特有的危险:代码生成速度太快,你来不及消化。
以前自己写代码,每一行都是你打的,就算没有刻意理解,写的过程里你的大脑也在处理这些逻辑。用 Claude Code 之后,几十行代码两秒钟就出来了,你的大脑处理速度根本跟不上生成速度。
我当时傻乎乎地以为,只要代码能跑、测试能过,不完全理解也没关系。
后来我意识到这个想法有多危险。
你不理解的代码,有三个直接后果:出了 bug 不知道从哪里下手 、需要改需求时不知道改哪里影响哪里 、别人(或者三个月后的你)接手代码时完全没法读 。
独立开发出海产品,这三个后果的代价都很高,因为没有人帮你兜底。
解决方法不是不用 Claude Code,是养成一个习惯:让它解释它自己写的代码 。
四个层次的解释,按需索取
Section titled “四个层次的解释,按需索取”不是所有代码都需要同等深度的解释。我把解释分成四个层次,根据代码的复杂度和重要性选择用哪个。
层次一:「这段代码在做什么」——快速扫描
Section titled “层次一:「这段代码在做什么」——快速扫描”适用场景:简单的工具函数、你大概看懂了但想确认的代码。
触发 Prompt:
用一句话告诉我这段代码在做什么,不需要解释细节
这个层次的目的是快速验证你的理解是否正确。如果你看代码的理解和它的一句话解释对得上,说明你真的懂了,可以继续往下走。如果对不上,说明你有误解,需要深入问。
层次二:「为什么这么写」——理解决策
Section titled “层次二:「为什么这么写」——理解决策”适用场景:有不明显的设计选择,或者你觉得「有更简单的写法,为什么它没用」的地方。
触发 Prompt:
这里你用了 [具体写法],为什么不用 [你觉得更简单的写法]?这个选择背后的原因是什么?
举个真实例子。它给我写了一段 Stripe Webhook 处理代码,里面用了:
const event = stripe.webhooks.constructEvent( body, signature, process.env.STRIPE_WEBHOOK_SECRET!)我看到 ! 这个非空断言,觉得奇怪,问了它:
为什么在
process.env.STRIPE_WEBHOOK_SECRET后面用!,而不是先检查它是否存在?
它的回答让我意识到一个我之前没想到的设计原则:Webhook secret 是部署时必须设置的环境变量,如果它不存在说明部署配置有问题,应该让程序崩溃而不是优雅处理 ——因为崩溃会立刻被发现,而优雅处理可能导致所有 Webhook 默默地被忽略,问题被掩盖。
这个「为什么」,改变了我对这段代码的理解,也改变了我以后写类似代码时的判断。
层次三:「这里可能出什么问题」——主动风险识别
Section titled “层次三:「这里可能出什么问题」——主动风险识别”适用场景:涉及支付、认证、数据删除、并发等高风险模块。
触发 Prompt:
这段代码在什么情况下会出问题?列出三个最可能的失败场景,每个说明触发条件和后果。
大多数教程不告诉你的细节在这里: 这个层次的解释,比「解释代码逻辑」更有价值。它问的不是「代码在做什么」,是「代码在哪里可能错」。
举个例子,我让它给我的 Stripe Checkout 回调处理写了这个解释:
失败场景一:Stripe 重试发送同一个 webhook,你的代码会重复处理
checkout.session.completed事件,可能导致数据库里创建两条订阅记录。触发条件:你的服务器在第一次处理时返回了非 2xx 状态码,Stripe 会重试。失败场景二:
session.customer为 null,你的代码会在后续的prisma.subscription.update里崩溃并返回 500,但 Stripe 会认为这次 webhook 失败并重试——触发失败场景一的循环。失败场景三:数据库操作超时,webhook 返回 500,Stripe 重试,同上。
看到第一个失败场景,我立刻去加了幂等性检查。这个问题如果在生产环境被用户触发,修复代价比提前加一个检查高一百倍。
层次四:「如果需求变了,我应该改哪里」——可维护性确认
Section titled “层次四:「如果需求变了,我应该改哪里」——可维护性确认”适用场景:比较复杂的模块,或者你知道这部分需求以后可能变化的代码。
触发 Prompt:
如果三个月后我需要 [描述一个可能的需求变化],我应该改这段代码的哪个部分?改动影响范围有多大?
这个问题直接测试代码的可维护性。一段好代码应该能清楚地回答「哪里改」和「影响多大」。如果它的回答是「需要改很多地方」或者「影响很广」,说明这段代码的结构可能有问题,值得现在就重构。
我用这个问题测过一段订阅状态同步的代码,问题是「如果以后要支持多个订阅计划,代码需要怎么改」。
它告诉我:你现在的代码里有三个地方硬编码了「一个用户只有一个订阅」的假设,如果支持多个计划需要全部修改,而且这三个地方逻辑耦合,改一个容易影响另两个。
当时需求没到,但我把这个信息记下来了。两个月后需求真的变了,我直接知道要改哪里,没有花时间重新理解。
批量解释:处理大段生成代码的方法
Section titled “批量解释:处理大段生成代码的方法”有时候它一次生成了几十行甚至上百行代码,逐段去问太低效。
我有一个固定的批量解释模板,在接收大段代码后直接用:
你刚才生成了这段代码。请按以下顺序解释:
- 整体逻辑是什么(两句话以内)
- 有没有不明显的设计决策——那些「为什么这么写而不是更简单的写法」的地方,列出来解释
- 这段代码的三个最可能失败的场景
- 有没有你做了但我没有明确要求的事情
第四条是关键。它会告诉你它「顺手做了什么」——这对应前面讲的 Karpathy 原则里「外科手术式修改」的问题。知道它做了什么你没要求的事,你才能决定要不要接受。
把解释变成注释:持久化你的理解
Section titled “把解释变成注释:持久化你的理解”光是问了理解了还不够,因为你的记忆会消退。
我的习惯是:问完解释,让它把关键的解释直接写进代码注释:
把你刚才解释的内容,用注释的形式加进代码里。不是每行都加,只在有非明显设计决策的地方加,说明「为什么这么写」而不是「这行在做什么」。
效果对比:
没有注释的代码:
const event = stripe.webhooks.constructEvent(body, sig, secret!)有解释性注释的代码:
// 使用非空断言而不是条件检查:secret 是部署时必须配置的环境变量// 如果它不存在说明部署配置错误,应该立即崩溃而不是静默失败const event = stripe.webhooks.constructEvent(body, sig, secret!)三个月后你看这段代码,不用再问一遍「为什么用 !」。
坑一:问了解释,以为理解了,其实没有
有一次我让它解释一段缓存逻辑,它解释完我说「好,我懂了」,然后 commit 了。
一周后需要改这段代码,打开来发现我还是不确定一个地方。回想了一下,我当时「好我懂了」的那一刻,其实只是没有立刻产生疑问,不等于真的理解。
解决方案: 听完解释不要马上说「好」,而是先用自己的话复述一遍:「所以说,这段代码的核心逻辑是 [你的理解],对吗?」让它来确认你的复述是否正确。复述对了才算真的理解。
坑二:问了「可能出什么问题」,它说「没有明显问题」,我就放心了
有一次我问它一段 Prisma 查询「有什么潜在问题」,它说「代码逻辑清晰,没有明显的问题」。
我放心了,上线了。
两周后发现那段查询在数据量大的时候非常慢,因为查询条件里有一个没有索引的字段。这是一个性能问题,不是逻辑问题,「没有明显的逻辑问题」不代表没有性能问题。
解决方案: 不要问「有什么问题」这种开放性问题,要按维度分开问:「有没有逻辑错误」、「有没有性能问题」、「有没有安全漏洞」。泛泛的问题得到泛泛的回答,分维度的问题才能覆盖不同类型的风险。
你用 Claude Code 写的代码,你要能在一年后不看 AI 就能解释它为什么这么写——做不到这一点,代码就不是你的,是它的。
用 AI 生成代码,责任还是你的。理解是这个责任的前提。
最后一个实际的问题: 你现在项目里有没有某段 Claude Code 写的代码,你其实不完全确定它为什么这么写——不是不会,是说不出「为什么是这个选择而不是另一个」?如果有,它在哪个模块,你打算什么时候去问清楚?