We can see a program as a succession of program states.
Each program states consists of several variables with values.
As a program executes, it processes these states and transforms them into a new states.
For instance, by reading variables and writing variables. This is the normal mode of operation.
Now, however, since in the beginning, we have a normal input and in the end we have a failure,
there must be a defect somewhere in our program that actually causes the problem.
So let me assume that this statement we're executing here actually has a defect.
What happens is that now, when executed, it introduces
an error in the program state which we call an infection.
This infection is now being propagated possibly to other state
and eventually becomes visible as a failure towards the user.
What we get in here is actually an entire cause-effect chain.
You see, these failures, which is an infection, is caused by earlier infections
and if we are at a state where the infection has no further origin, that is, the input state is the same,
and the output is infected, and we know the statement that was executed at this precise moment
which caused this transition from the same state to infected state,
this is the statement which caused the infection, this is the statement which has the defect.
When we're debugging now, we need to identify this cause-effect chain
not only do we need to identify but we also need to break the cause-effect chain.
If we can break this cause-effect chain from defect to failure, then we're done with debugging.
So all of this looks very simple, however, in real life, it's much more complicated than that.
To start with, not every defect automatically causes a failure.
It may well be that the defect causes an infection which later simply is not
propagated as a real life infection just as well.
So the infection is not propagated and never ever becomes visible to the user.
It may not even cause a failure at all or the statement with the defect may not even be executed
or only under very specific circumstances may actually cause an infection and later a failure.
This is the problem of testing. You can execute a program again and again,
never have a failure and still have a defect in there, however, if a program fails,
that is if we actually see a failure, then we can always trace it back to the defect that causes it.
So if there's a failure, we can always fix it by following back the cause-effect chain.
But then the next problem is: these states are huge.
So over here we have 1, 2, 3, 4, ... 12 variables. Cute.
In reality, we have 10,000 of such variables and not only do we have 10,000 of such variables,
we also have 10,000 of steps between defect and failure.
So tracing back the cause-effect chain can be much,
much more complicated that it is in this simple picture.
The longer the cause-effect chain, that is the longer the time we have to cover,
the more states we have to cover, the harder is to debug it.
And also, the larger the state, the more we have to search for an infection.
Again, this makes debugging harder and harder.
It's like finding a needle in a haystack except that the haystack
sometimes is larger than any haystack you'll ever find on earth.
Un programma lo possiamo vedere come una successione di stati di programma.
Ogni stato di un programma e' composto da variabili con dei valori.
Mentre un programma e' in esecuzione, elabora questi stati e li trasforma in nuovi stati.
Per esempio, leggendo le variabili o modificandole. Di norma e' cosi' che opera.
Ora comunque, fin dall'inizio, abbiamo un normale input e alla fine un guasto,
ci dev'essere da qualche parte un difetto nel nostro programma che causa in effetti il problema.
Lasciami quindi supporre che questa istruzione che eseguiamo qui abbia percio' un difetto.
Cio' che succede ora e' che, quando viene eseguita, introduce
un errore nello stato di programma che chiameremo "infezione".
Questa infezione si sta ora propagando probabilmente in altri stati
ed eventualmente diventa visibile all'utente come guasto.
Cio' che abbiamo qui e' effettivamente un'intera catena di cause-effetti.
Vedi, questi guasti, che sono un'infezione, e' provocata da infezioni precedenti
e se siamo in uno stato in cui l'infezione non ha un'ulteriore origine, cioe' lo stato e' l'input stesso
e l'output e' infettato, allora sappiamo che l'istruzione eseguita in questo preciso momento
ha causato la transizione dallo stato attuale allo stato di infezione,
questa e' l'istruzione che causa l'infezione, cioe' l'istruzione che contiene il difetto.
Mentre stiamo facendo debugging, dobbiamo individuare questa catena di causa-effetto,
e non dobbiamo solo individuarla ma dobbiamo anche spezzare la catena causa-effetto.
Se riusciamo a spezzare questa catena che va da difetto a guasto, avremo concluso il debugging.
Tutto cio' sembra quindi molto semplice, comunque, nella vita reale, e' molto piu' complicato di cosi'.
Tanto per cominciare, non tutti i difetti causano automaticamente dei guasti.
Puo' benissimo essere che un difetto causi un'infezione che, in seguito, semplicemente non
si propaghi cosi' direttamente in un'infezione reale.
Quindi l'infezione non verra' propagata e non sara' mai visibile all'utente.
Potrebbe non causare nemmeno un guasto o che l'istruzione difettosa non venga mai eseguita
o solo in certe situazioni molto particolari potra' causare un infezione e in seguito un guasto.
Questo e' il problema con il testing. Potresti eseguire un programma molte e molte volte,
mai avere un guasto e avere comunque dentro un difetto, pero', se un programma si guasta,
cioe' se vediamo in effetti un guasto, allora potremo sempre tracciare a ritroso il difetto che l'ha provocato.
Quindi se c'e' un guasto, possiamo sempre sistemarlo seguendo a ritroso la catena cusa-effetto.
Ma poi il problema sara': gli stati sono moltissimi.
Allora qua abbiamo 1,2,3,4, ... 12 variabili. Bella li'.
In realta' ne abbiamo 10.000 di queste variabili e non solo ne abbiamo 10.000,
ma abbiamo anche 10.000 passaggi tra difetti e guasti.
Quindi tracciare a ritroso la catena causa-effetto puo' essere molto
molto piu' complicato di quel che si vede in questa semplice immagine.
Piu' lunga e' la catena causa-effetto, piu' tempo metteremo a percorrerla,
piu' stati avremo da percorrere, piu' dura sara' farne il debugging.
E anche, piu' grandi saranno gli stati e piu' dovremo stare a cercare l'infezione.
Di nuovo, cio' rendera' sempre e sempre piu' dura farne il debugging.
E' come cercare l'ago in un pagliaio, solo che l'ago
a volte e' molto piu' grande di qualsiasi ago che potreste mai trovare sulla Terra.
プログラム状態が連なって
プログラムを形成しています
それぞれのプログラム状態は
変数と値からできています
プログラムを実行すると
これらの状態は新しい状態に変えられます
例えば変数を読み取って書き出す行為です
これがノーマルモードです
しかし初めはノーマル入力だったのに
ここで最後に不具合が出ています
プログラム内に問題の原因となる
欠陥があるということです
実行したこの命令文に欠陥があると仮定しましょう
今起きていることはなんですか?
実行します
プログラム状態にエラーが出ました
これが感染です
この感染はおそらく
他の状態へと拡大されていきます
そして最終的に
ユーザにも不具合が見える形になります
ここで起こっているのは原因と結果の連鎖です
不具合つまり感染が確認できます
前の感染が原因で起きています
原因となる感染がない状態の場合
入力状態は問題ないですが
出力が感染しています
この時に実行した命令文を見ます
感染なしの状態を
感染状態に変えた原因となる命令文です
これが感染の原因となった命令文
つまり欠陥のある命令文なのです
デバッグする時は
この原因と結果の連鎖の特定が必要です
特定するだけでなく
この連鎖を中断させる必要もあります
欠陥から不具合に至る連鎖を中断させれば
デバッグは終了です
簡単そうに見えますが
実際は見た目よりもはるかに複雑です
欠陥がすべて不具合の原因になるわけではないからです
この欠陥が感染を起こしていたとしても
本物の感染のように
拡大していくものではありません
感染は広がらずユーザにも見えない
他に不具合も引き起こさないし
欠陥のある命令文もありません
ある特定の状況下でだけは感染し
不具合を起こすことはあるかもしれません
これは実験上の話なので
プログラムは何度も作成できます
不具合も欠陥もありませんが
いざ本当にプログラムが失敗すると
つまり実際に不具合を確認した場合は
原因となった欠陥をたどることになります
原因と結果の連鎖をたどることで
常に修正することはできます
しかしここでまた問題が生じます
状態が大量にあるいうことです
数えてみるとここには変数が12ありますね
かわいいものです
実際にはこのような変数が一万あります
一万あるのは変数だけではありません
欠陥と不具合の間に
一万ステップあるということです
ですから原因と結果の連鎖をたどることは
この画面上の状況よりも
はるかに複雑なものとなります
原因と結果の連鎖が長ければ
処理する時間も長くなりますし
処理する状態も増えて
デバッグがますます困難になります
状態が大きければ
感染をさらに調査しなければなりません
こうしてデバッグがどんどん難しくなるのです
干し草の山の中から針を見つけるくらい至難の業です
想像を絶するほど大きな
干し草の山になることもありえます
我们可以把程序看作一些连续的程序状态。
每个程序状态包括若干带值的变量。
程序运行时,它处理这些状态,并把它们转到新的状态。
比如,通过读写变量。这是操作的正常模式。
然而,因为一开始我们有标准的输入,最后却得到一个失败。
就一定是我们程序某处的一个defect,引发的这个问题。
所以先假设我们这里执行的这个声明有一个defect缺陷。
现在就是,执行时,它就引入
一个错误到程序状态中,我们把这个称为infection感染。
这个感染点现在可能会传染到别的状态,
最后形成一个可视的失败,反馈给用户。
我们这里就得到一个完整的诱因-影响 的关系链。
你看,这些失败和感染,是由之前的感染状态引起的,
如果一个状态的感染没有更早的源头,即输入状态相同,
输出却被感染,则在此状态前面执行的那个声明
导致了从正常状态到感染状态的转变,
就是这个声明语句引发了感染,它有缺陷。
当我们现在调试时,我们就要识别这条因果链,
不只是识别,还要斩断这条因果链。
如果我们可以破坏这条缺陷到失败之间的因果链,则算完成了调试。
所以这一切看起来很简单,然而,现实中,比这复杂得多。
一来,不是每个缺陷都会自动导致失败。
可能缺陷引起了感染,但感染后,
并没有像现实中的传染病那样传染。
所以,感染点没有传染,就不会被用户看到。
它甚至完全不会导致失败,或有缺陷的声明甚至没有执行
还可能只在特定的环境下会引发感染,进而失败。
这就是测试的问题了。你可以一遍遍地运行程序,
从不失败,却仍有缺陷。但是,如果程序失败了,
即我们看到一个失败了,却总能追溯到引发失败的缺陷。
所以如果程序失败,我们总能修复它,沿着因果链去追寻。
但下一问题就是,这些状态太多了。
这里我们有12个变量,已经是很少了。
现实中,我们有一万个变量,除了这一万个变量,
在缺陷和失败之间还经过了一万步的执行。
所以追踪这条因果链就变得
非常复杂,远非简单情况可比。
因果链越长,用于追踪的时间就越长,
追踪的状态就越多,调试就越难。
同样,状态越大,查找感染需要的一切就越多。
这也会让调试变得越来越难。
就好像在一个稻草堆里找根针,并且这个稻草堆
可能比你在地球上见过的所有稻草堆还要大。