プログラマが知るべき97のこと/エラーがエラーを相殺してしまう
コードは嘘をつきません。プログラムは必ず人間が書いたとおりに動きます。ただ、ある箇所で書いたことと、別の箇所で書いたことが矛盾していた場合、思いがけない動きをすることがあり得ます。
アポロ11号の月着陸船のソフトウェア設計を指揮したAllan Klumppは、あるインタビューで、エンジンを制御するソフトウェアにバグがあったことを明かしています。そのバグのせいで、着陸船の動きが不安定になる恐れがあったというのです。しかし、結果的にそのバグは別のバグによって相殺されたため、発見も修正もされず、アポロ11号、12号の月着陸は無事成功しました。
処理が完了したかどうかを戻り値で知らせる関数があったとしましょう。そしてその関数に、本来trueを戻すべき時にfalseを戻す、というバグがあったとします。さて、呼び出した側の関数が、戻り値のチェックをしない作りになっていたとしたらどうでしょうか。ある日誰かが戻り値のチェックをしていないことに気づいてチェックするコードを加えるまでは、何事も起きず、何も問題なく動いているように見えるのではないでしょうか。別の例を挙げましょう。状態をXML ドキュメントのかたちで保持するアプリケーションがあったとします。しかし、あるノードの書き込みの際、仕様ではTimeToDieとすべきと書いてあるところを、誤ってTimeToLiveとしてしまっていたとしたらどうでしょうか。この場合、XML書き込みのコードと読み込みのコードがどちらも同じように誤っていたとしたら何も問題は起きません。しかし、どちらか一方を修正してしまった場合、あるいは、また新たなアプリケーションが同じドキュメントを読み込んだ場合には、平衡状態が崩れ、問題が露呈することになります。
表面上起きている問題は1つなのに、それに関わっているコードが2箇所ある場合、一つ一つ不具合を修正していくという方法で対処していくと問題が解決しない恐れがあります。バグの報告を受けると、プログラマは原因となる箇所を見つけ出して修復し、テストをします。バグの原因となっていたコードが元々2箇所だったとしたら、それでは問題は解決しないでしょう。2つ目の不具合は修正されていないからです。その場合は、最初の修正はいったん元に戻し、またコードを調べて別の不具合を探すことになるでしょう。それで2つ目の不具合を見つけ出し、修正をしたとします。しかし、1つ目の不具合の修正は元に戻っていますから、やはり問題は解決しません。問題が解決しなければ、修正は元に戻されることになります。これが何度か繰り返されることも多いですが、やがて人によっては、どちらの不具合も問題には関係ないとみなし、第3の不具合を探し始めることもあります。しかし、それが見つかることは決してないのです。
表面上1つに見える問題が、実は2つのコードの相互作用で生じている、という場合は、このように原因となる箇所の特定が難しく、袋小路に入ってしまうことが珍しくないのです。実は早い段階で原因となる箇所を見つけてはいるのですが、2つが同時に関与しているとはなかなか気づきません。
こういうことが起きるのはコードだけではありません。要件文書に誤りがあることもあるからです。この誤りはウイルスのようにあちこちの部分に広がる恐れもあります。コードのエラーが、要件文書のエラーを相殺し、表面上は問題が無いように見えてしまうのです。
問題は人間にも広がります。ユーザは「このアプリケーションが「左」だと言えば、それは「右」という意味だ」と学習すると、自分の行動をそれに合わせて調整することがあるからです。新規のユーザが加わった時には、「このアプリケーションが「左ボタンをクリックしろ」と指示したら本当は「右ボタンをクリックしろ」という意味なので注意するように」と伝えたりもします。そのバグが後で突然修正されてしまったら、ユーザは慣れるまでしばらく混乱し、誤操作を繰り返すことになります。
原因となる箇所が1つなら見つけて修正するのは簡単です。しかし問題に複数の原因がある場合は、複数の修正が必要になり対処が難しくなります。また、修正が容易な不具合は、比較的早い段階で修正されるのですが、修正が難しい不具合の修正は、どうしても後に回されるということもあります。
複数の原因がある問題にどう対処すべきか、「こうすれば大丈夫」というような簡単な解決策はありません。まず大切なのは、そういう問題が存在し得ると認識することでしょう。そして、思い込みを捨ててあらゆる対処を思いつくよう、冷静さを保つことが重要です。