我们可以把程序看作一些连续的程序状态。

程序运行时,它处理这些状态,并把它们转到新的状态。

比如,通过读写变量。这是操作的正常模式。

然而,因为一开始我们有标准的输入,最后却得到一个失败。

就一定是我们程序某处的一个defect,引发的这个问题。

所以先假设我们这里执行的这个声明有一个defect缺陷。

一个错误到程序状态中,我们把这个称为infection感染。

这个感染点现在可能会传染到别的状态,

最后形成一个可视的失败,反馈给用户。

我们这里就得到一个完整的诱因-影响 的关系链。

你看,这些失败和感染,是由之前的感染状态引起的,

如果一个状态的感染没有更早的源头,即输入状态相同,

导致了从正常状态到感染状态的转变,

就是这个声明语句引发了感染,它有缺陷。

当我们现在调试时,我们就要识别这条因果链,

不只是识别,还要斩断这条因果链。

如果我们可以破坏这条缺陷到失败之间的因果链,则算完成了调试。

所以这一切看起来很简单,然而,现实中,比这复杂得多。

一来,不是每个缺陷都会自动导致失败。

所以,感染点没有传染,就不会被用户看到。

还可能只在特定的环境下会引发感染,进而失败。

这就是测试的问题了。你可以一遍遍地运行程序,

从不失败,却仍有缺陷。但是,如果程序失败了,

即我们看到一个失败了,却总能追溯到引发失败的缺陷。

所以如果程序失败,我们总能修复它,沿着因果链去追寻。

但下一问题就是,这些状态太多了。

这里我们有12个变量,已经是很少了。

现实中,我们有一万个变量,除了这一万个变量,

在缺陷和失败之间还经过了一万步的执行。

追踪的状态就越多,调试就越难。

同样,状态越大,查找感染需要的一切就越多。

这也会让调试变得越来越难。