豪鬼メモ

一瞬千撃

悪魔の証明とユーザサポート

「見つからないことは、存在しないことの証明にはならない」
これは、消極的事実の証明とか、悪魔(神)の不在証明とか言われる類の命題の困難さを示す格言だ。プログラムの完全性の証明も、不具合の不在証明と同義なので、同様に困難だ。このことは、しばしばデバッグの際の論争の種になる。
f:id:fridaynight:20220106222926p:plain:w200


なんでこんなことを突然書いたかというと、たまにライブラリのユーザサポートで困ることがあるからだ。「あなたのライブラリを使っている私のシステムが落ちた」という報告をいただくことがある。その場合、私は、原因の究明をしたいと願う。私のライブラリに原因があるのなら、ぜひとも直したい。沽券に関わる問題だ。

どういうアクセスパターンでどういう不具合が起きるのかをまず知りたくなる。どのクラスのどのメソッドをどの順番でどんなパラメータで読んでいるのか。並列呼び出しをしているのかどうか。ファイルサイズやレコードのデータサイズはどうか。ログやスタックトレースから何かわかるか。対話しながらそんなことを聞き出して、原因を探る。その過程でこちらに思い当たる節があれば、こちらのコードを調査する。ユーザ側の使い方に問題があるとわかれば、それを指摘して直してもらう。

問題は、それで解決しなかった場合だ。落ちたという観測は事実として受け入れるとして、何が原因で落ちたかは分からない状態ということだ。ここで、仮説空間は二つに大別される。アプリ側のコードにバグがあるのか、ライブラリ側のコードにバグがあるのかだ。その切り分けをするには、テストを書くのが常套手段である。アプリ側のコードを疑うのであれば、ライブラリの実装をダミーかモックに切り替えたテストケースを書くべきだ。ライブラリ側のコードを疑うのであれば、アプリ側のアクセスパターンを模したテストドライバでライブラリの実装を呼び出すテストケースを書くべきだ。

さて、こんなユーザがいたとしよう。曰く、「プロダクション環境で落ちた。自分のコードを読む限り、アプリ側のコードに問題はない。よって、ライブラリに問題がある」。自分のコードに問題がないと言い切るあたりで多少驚きつつも、私はまず対話によって問題の解決を図る。しかし、私が自分のコードを読む限りでも、私のコードに問題を発見できなかったとする。

こうなると、テストを書くしかない。私はアプリ側のコードを読む立場にはないので、そのアクセスパターンを模したテストケースをユーザ側で書いてくれと提案するしかない。そして、実際に落ちるテストケースが書けたなら、ぜひこちらに送ってくれと。そしたら直すから。なにしろ沽券に関わる。

そんな場合に、こう言われてしまうと非常に困る。「プロダクションのコードが複雑すぎて、問題を再現するテストケースが書けない(書きたくない)」。しかし、それを聞くと私は思うのだ。自分で複雑すぎると言うコードの完全性によくもそんなに自信が持てるなと。そして、そういうことを言ってくるケースでは、アプリ側のテストコードがないのが常だ。そもそも、ちゃんとテストを書く開発者であれば、最初の報告の時点で不具合の再現手順を示してくれたり、テストコードまでつけてくれたりするからだ。

こうなると、非常に不本意ではあるが、対立的な構造にならざるを得ない。私はこう言うしかない。「私のコードにバグがないということは証明できないし、するつもりもない。消極的事実の証明になるからだ。この場合、立証責任はあなたにある。バグが存在することをあなたが証明するのだ。そのためには、再現手順をテストコードとして示していただきたい」。

ああ、なんて私は嫌な奴なんだ。しかし、相手がベテランであろうが大統領であろうが、そう言わざるを得ない。だって、バグがあるって知っているなら、直してるよ。少なくとも直そうと努力はするだろうよ。私のコードがバグを抱えている可能性は普通にあると思っている。でも、心当たりがないし、再現もできないなら、直せないよ。なんか修正をしたとして、直ったかどうか確認する術もないし。見えない敵とは戦えないよ。いじわるしたいわけじゃないんだよ。助けてあげたいんだよ、できるなら。まじで。

実際問題としては、対立姿勢を見せつけても誰も得をしない。なので、「このままでは何も判断できません。ともかく、再現できないと直せないので、一緒に方法を探りましょう」的なポジティブな言い方で補填する場合が多い。時間と余力がある時は、対話を通して推測したアクセスパターンを私がテストコードに落とすこともあれば、そんなような作業をしてくれるように誘導することもある。「ライブラリ内でbus errorが起きたんだから、ライブラリの不具合に決まっている」などと強硬に主張してくるケースでも、アプリ側の不具合で同じファイルを二重に開いて競合を起こしていたというオチが付いたりする。一通りの努力をしても埒が明かなければ、「再現させられるようになったら教えてね」で一旦区切るしかない。

話をまとめる。プログラムの完全性は誰も証明できないので、デバッグ作業に参加する全ての人は、謙虚になるべきだ。あるシステムの不具合を観測したのは事実として認定しても、不具合を引き起こした原因についての結論に飛びついてはいけない。何が観測事実で何が仮説かを明確に分けて、まず皆で共通認識を持つことが必要だろう。不具合の内容やシステムの構成についての有用な情報もできるだけ共有し、不具合の原因の仮説をひとつひとつ検証していくべきだ。その際には、証拠が出るまでは、全ての仮説は推測にすぎないということを肝に銘じるべきだ。

他者の担当モジュールに不具合があると主張したい場合、その主張者は、その他のモジュールの完全性を根拠としてはならない。完全性の証明などできないからだ。一方で、不完全性の証明はできる。該当のモジュールを孤立させた環境において、不具合を再現する手順を明確にすれば良いだけだ。その責任は、主張者にある。言い換えると、他人のバグを指摘する際には、不具合の客観的な再現手順を明確にすることが強く期待される。そして、その手順は簡潔であるほど望ましく、関わるモジュールは少ないほど望ましい。それを楽にするのが、テストコードだ。テストを書こう。少なくとも、テストの裏付けがない主張は説得力に欠けることをわきまえよう。