HackerTube ?
主题阅读

为什么测试全绿,程序还是错了?

Gary Bernhardt 的隔离测试和 Andrej Karpathy 的训练演示里,代码出错时,屏幕上都可能传来好消息。

改完代码,测试全绿。你有理由比刚才更放心。可如果有人问「哪种错误即使还在,这些测试也会绿?」,你会想到什么?两位讲者各给了一个容易漏掉的答案。

测试替身和代码可以一起搞错

Gary Bernhardt 在《Boundaries》里讲了一个例子。程序要找出到期的用户,再发出邮件。为了单独测试这段流程,可以用一个替身代替真实的用户查询。你预先告诉它会查到谁,测试就能很快检查下一步有没有调用邮件服务。

这里藏着一个循环:写代码的人对真实接口有一个理解,又按同一个理解写了替身。如果理解错了,代码和替身依然可以配合得天衣无缝。绿色结果告诉你流程在这个约定下能走通;真正的用户查询是否也遵守这个约定,还得去另一处看。

if you don't stub the boundary correctly, your test will pass and your production system will be wrong.
Gary Bernhardt · Boundaries · 英文字幕
回看原片 4:14–4:25 →

连真实运行的程序,也可能给你好消息

看完第一个例子,很容易想到「那我把真的程序跑一遍」。Karpathy 恰好这么做了。他亲手写了一个小型神经网络,训练时用梯度决定每个参数往哪个方向调。预测越来越接近答案,训练误差降得很快。然后他指出了代码里的一处错误。

这段训练本该在每轮重新计算前把旧梯度清零,程序却把上一轮的数值也加了进来。这个小问题恰好很容易学,累加让参数迈出了更大的步子。修好之后,误差反而降得慢了一些。只看「误差有没有下降」,两个版本都会过关,错误版本甚至更讨喜。

you may have lots of bugs in the code and uh your network might actually work
Andrej Karpathy · 手写 micrograd · 英文字幕
回看原片 2:13:44–2:13:51 →
试两轮:忘了清零,误差还会降吗?

用一个数 w 代替整个网络。它从 4 出发,目标是 0;每轮减去梯度的四分之一。取误差 w²/2,此时梯度恰好等于 w。

参数:4 → 3 → 2.25
第二轮用梯度 3,误差从 8 降到约 2.53。
参数:4 → 3 → 1.25
第二轮用了 4 + 3 = 7,误差从 8 降到约 0.78。仅看误差下降,错误版本也过关。

让你怀疑的错误有机会露面

把两段放在一起看,第一处要追问的是「换回真实依赖,这个约定还成立吗?」;第二处已经跑了真实代码,要追问的是「照这个标准,做错了也能通过吗?」这两种疑问需要不同的检查。

下次评审代码时,可以挑一条正在通过的测试,试着说出一个它应该抓住的具体错误。比如「用户查询返回的结果和替身约定的格式不同」,那就用一次真实查询对照两者。换成「第二轮更新混入了第一轮的梯度」,那就挑一个能手算两轮的小例子,直接核对第二轮的更新值。再有意引入你怀疑的错误,看看检查会不会变红。

同样是一盏绿灯,这时你能更准确地说出自己有把握的那件事。