我们可以把程序看作一些连续的程序状态。
程序运行时,它处理这些状态,并把它们转到新的状态。
比如,通过读写变量。这是操作的正常模式。
然而,因为一开始我们有标准的输入,最后却得到一个失败。
就一定是我们程序某处的一个defect,引发的这个问题。
所以先假设我们这里执行的这个声明有一个defect缺陷。
一个错误到程序状态中,我们把这个称为infection感染。
这个感染点现在可能会传染到别的状态,
最后形成一个可视的失败,反馈给用户。
我们这里就得到一个完整的诱因-影响 的关系链。
你看,这些失败和感染,是由之前的感染状态引起的,
如果一个状态的感染没有更早的源头,即输入状态相同,
导致了从正常状态到感染状态的转变,
就是这个声明语句引发了感染,它有缺陷。
当我们现在调试时,我们就要识别这条因果链,
不只是识别,还要斩断这条因果链。
如果我们可以破坏这条缺陷到失败之间的因果链,则算完成了调试。
所以这一切看起来很简单,然而,现实中,比这复杂得多。
一来,不是每个缺陷都会自动导致失败。
所以,感染点没有传染,就不会被用户看到。
还可能只在特定的环境下会引发感染,进而失败。
这就是测试的问题了。你可以一遍遍地运行程序,
从不失败,却仍有缺陷。但是,如果程序失败了,
即我们看到一个失败了,却总能追溯到引发失败的缺陷。
所以如果程序失败,我们总能修复它,沿着因果链去追寻。
但下一问题就是,这些状态太多了。
这里我们有12个变量,已经是很少了。
现实中,我们有一万个变量,除了这一万个变量,
在缺陷和失败之间还经过了一万步的执行。
追踪的状态就越多,调试就越难。
同样,状态越大,查找感染需要的一切就越多。
这也会让调试变得越来越难。