プログラムは思った通りには動かない。書いたとおりに動くのだ

Any code doesn't run as you thought, run as it wrote

2015.06.17

Updated by Ryo Shimizu on 6月 17, 2015, 10:13 am JST

 プログラマーが嫌いなもの、それはバグです。
 バグが好きなプログラマーはあまりいません。
 しかしバグをとる作業、いわゆるデバッグが好きなプログラマーはたまに居ます。
 しかも、自分のプログラムではなく、他人のプログラムのバグをとるという作業は、一種のパズルのようなもので、そういう複雑なプログラムのバグを発見したときには、一種恍惚とした感情が生まれます。
 ところでバグとはなんでしょうか。
  平たく言えばプログラミング上のミスのことです。
  複雑な論理を自在に使いこなすプログラマーがたびたびミスを犯すというのは奇妙に感じられるかもしれません。
 とはいえ そもそもミスをしない人間などいないのです。
  しかしミスを自覚できる人は一体どれくらいいるのでしょうか。
  たとえば飲食店を経営するとしましょう。
  なんとか開店までこぎつけ、営業を開始したとします。
  出足は順調、客もついてきました。店主は満足気に笑うでしょう。しかし営業開始後、半年、一年して客足が鈍り、常連客も一人減り、二人減り・・・ついには閑古鳥。最後は借金を抱えて店を畳むことになります。
 「一体何がいけなかった?味か?立地か?サービスか?」
 経営者は頭を悩ませ、苦悩したはずです。しかしそのどれかなのかもしれず、もしくはその全部なのかもしれない。わからないのです。
  さて、この人物はどこでミスを犯したのでしょうか。
  飲食店が上手くいくかどうかは様々な複合的な要因があるから一概には言えません。
  重要なのは危機を事前に察知できるかどうかです。
  危機は察知されるまでは危機ではないからです。
  たいていの人間は、危機を危機と認識できぬまま、ミスをミスと認識できぬまま日々を過ごし、それが目前に迫った時には既に手遅れとなります。
 むしろ人は常に判断ミスを犯しているはずなのです。
 そのミスを察知する能力がないだけで、ミスをしていないことにはなりません。
  気がつけば取り返しのつかないことになっているのです。
 プログラマーの職業的美点をひとつ上げるとすれば、他のどの職業人よりも自分が無能であることに自覚的であることです。
 プログラマーは常に自分自身の生み出したミス、すなわちバグとの戦いを余儀なくされます。
  自分のバグに自分でケリを付けられないプログラマーは半人前です。
 だからプログラマーは自分の生み出したバグという怪物ととことん戦うことになります。
 そして悲しいかな、プログラミング歴をいくら重ねてもつまらないバグは繰り返してしまいます。
 しかし経験を積んだプログラマーは大きなバグは繰り返しません。
 経験豊富なプログラマーは大きなミスを事前に防ぐ方法を知っているからです。
  ソフトウェア工学の歴史とは、大げさにいえば人類が自らの無能と戦うための方法論の歴史でもあります。
 たとえばどれだけ研鑽を積んでも常に繰り返してしまうミスが、タイプミスです。
 つまりキーボードの打ち間違いです。
 こればっかりはどうにもなりません。
 プログラマーが操るプログラミング言語には数百の命令語があり、さらにそこから利用するクラスライブラリやフレームワークの命令を合わせるとその語彙は数千から数万に膨れ上がります。一人の人間が把握することがほとんど最初から不可能と言っても良いほどの膨大さです。
 ある程度の規則性があるとはいえ、記号や大文字小文字をひとつ間違えただけでバグになってしまうため、プログラマーがタイプミスを防ぐためにはかなりの工夫が凝らされています。
 ひとつはエディタの機能です。
 プログラマーが文章やプログラムを書くときに使うエディタというアプリには制御構造を色分けする機能が備えられており、コード補完を使えばプログラムを書きながらプログラム自身をチェックできるという高度な機能があります。
 もう一つはコンパイラ、またはLint(リント)と呼ばれるソフトウェアによって行われる、プログラムの「検証」があります。
 コンパイラやLintは渡されたプログラムを注意深く検証し、明らかな誤りがあればエラーとして報告し、明らかな誤りではないものの、バグの混入する可能性が高い部分に関しては警告(ワーニング)を報告してくれるのです。
 プログラマーはこうした高度なケアレスミス対処手段に保護されているため、たとえタイプミスをしてもすぐにその原因を突き止め、修正することができるようになっています。
 タイプミスなどはどれだけ経験を重ねても完全になくすことはできないのですが、この程度のミスならおそるるに足りません。人間が日常生活の中で言い間違いをすることなどしょっちゅうではないですか。
 もっと難しいバグはエラーも警告も出ないバグです。
 機械ごときが発見できるエラーなど、バグのうちにも入らないのです。
 機械が誤りを指摘した部分を修正するのは、単なる作業です。
 しかし、機械が「問題なし」として検証したプログラムの動作が大問題であることは日常茶飯事です。
 ここからがプログラマーの本当の戦いです。
 「プログラムは思った通りには動かない。書いたとおりに動くのだ」
  詠み人知らずの格言です。
 プログラムがバグっている原因の大半は、プログラマー自身の勘違いであす。
 つまりプログラマーは常に、自分が何をどう勘違いしていたのか、機械に現実を突きつけられることになります。
 デバッグとは自分自身と見つめ合うことなのです。
 そしてそういうバグは発見が容易なものもあれば、困難なものもあります。
 発見が容易なバグは、例えばプログラマーが意図した動作をしない、といったものです。
 たとえば「サインイン」というボタンを押したらサインインされるはずなのに、サインインされないとか、ゲームが始まったはずなのにキャラクターが動かないとか、そのたぐいです。
 これもまあ、たいして頭を悩ませるほどのバグではありません。
 たいていは勘違いかケアレスミスです。
  本当に恐ろしいバグは、プログラマーが認識できないところにいます。
  つまり、なぜ飲食店が潰れたのかわからないのと同じ、原因不明のバグです。
 原因不明のバグは、どのように発見されるのでしょうか。
 筆者は一時期、デバッグ請負人でした。
 デバッグ請負人とは、まあそんな言葉はないのですが、要は自分のプログラムではなく、他人のプログラムのバグを追求するという仕事です。
 自分のプログラムではないのでデバッグの難易度は数段上がるしストレスもたまります。
 しかし困難なかわりに高給を保証されていたので悪くない仕事でした。
 例えば、ゲームのオープニングムービーが10回再生されると11回目でプログラムが止まってしまうというバグがありました。
 オープニングムービーは無限に再生されるようにプログラミングしたはずなのに11回目で止まってしまうのです。
 これは何故だ、というわけです。
 普通に考えても原因が全く分からなくて混乱します。
 なにしろ、ムービーを再生する部分はただ一文、「play」という関数を呼び出しているだけなのです。それで私が呼ばれたわけです。
 こういう場合のバグは数パターンに分類することができます。
 この場合は「メモリリーク」というパターンでした。
 プログラムが同じ動作を繰り返しているはずなのに数回で停止するというのは、繰り返し(ループ)の中でメモリを適切に解放していないことに原因があります。
 筆者は即座にメモリリークだと判断して当該プログラムを調べて見たのですが、プログラムそのものに問題はありませんでした。
 とすると、問題はゲームプログラムでなくてOSの方にあると推定できます。
 そこでそのOSの開発者に電話を掛けて、「これこれこういうことが起きている」と報告すると、2時間後には修正されたOSが送られてきました。「確かに動画再生中にメモリリークしてる箇所があった」とのメモ付きで。
 修正されたOSで当該プログラムを動かすと、問題なく11回目以降も繰り返されるようになりました。
 このように、バグの原因を突き止めるのは経験と勘が必要ですし、時にはOSや環境自体のバグを疑う必要もあります。なかなか骨の折れる仕事なのです。
 しかし、デバッグ請負人とて、バグが特定されていなければバグを修正することも原因を推測することもできません。
 そうしたバグを徹底的に見つける専門職が、テストエンジニアです。
 テストエンジニアはバグを発見するための専門のエンジニアで、彼らの主な仕事はテスト計画の策定と実際のテストの実施という二段階に分けられます。
 テスト計画とは、プログラムの仕様書やソースコードを元に、「このプログラムにはどのような問題が発生しそうか」ということを事前に想定してプログラマーが想像もつかないようなことまで細かく検証した書類です。
 例えば数値が入力できるアプリケーションならば、「1を入力する」「10を入力する」「99を入力する」といったように執拗に細かくテスト項目を作るのです。
 プログラマーにしてみれば「そんなの、数値を入力するようになってるんだから、1だろうが10だろうが99だろうが9999だろうが同じだよ」と思ってしまいがちですが、これがそもそも勘違いである可能性があります。
 たとえば「9999」は入力できても「99999」が入力できないということは実際によくあります。
 16ビットで表現できる整数の最大値は6万5535であり、9万9999は表現できないからです。
 その数値がアプリケーションを利用するときに実際に必要なのかどうかを検証し忘れていることはよくありまして、テスト計画を事前に作ることがいかに重要なのか、仮に9万9999を入力する必要がないとしても、想定以上の数値が入力されたらどうするか、ということくらいは事前に確認しておく必要があるのです。
 こうした地道な計画を考え、プログラマーの思考の欠落を埋めるのがテストエンジニアという仕事です。
 テストエンジニアこそが唯一、プログラマーの見落としたミスを事前に察知し、修正の機会を与えてくれる存在なのです。
 これは非常に誇り高く尊敬を集める職業の一つです。
 実際、ソフトウェア企業ではテストエンジニアはプログラマーよりも給料が高いことが少なくありません。
 テストエンジニアは極めて希少な才能であり、ソフトウェア企業にとってなくてはならない存在だからです。
 ところが一般的な企業の業務ではこのテストエンジニアに相当する役職がないのです。
 つまりミスを事前に察知することも、それに対策することもできないということになります。
 上役の犯した大きなミスは有耶無耶にされ、平社員が書類のタイプミスをしただとか取引先に送付する書類の様式を間違えたとかという小さなミスが盛大に叱られるのが日常ではないでしょうか。
 上役のミスはごまかされやすいのです。
 というのも、ミスをミスとして認識すれば自分の失点になってしまうので、必死で他の理由を見つけてミスをなかったことにしてしまうからです。これでは会社はおかしくなってしまいます。仕事を進めることよりも失点を作らないことを優先すれば当然そうなります。そして事実、そのようにしておかしくなった会社はたくさんあるのです。
 プログラマーはミスを失点とは考えません。
 ミスは不可避なものであり、ミスをしないように無理に取り繕えば必ず大きなミスにつながります。
 それよりもミスを積極的に発見し、それを繰り返さないように知見を蓄積していくのがプログラマー的な考え方です。
  ではプログラマーは仕事のミスに対してどう対処するのでしょうか。
 タイプミスのような小さなミス。これは必ず起きます。
 必ずミスが起きることを前提として、ダブルチェックなどの機構を設けます。
 よく、経理が使い込みをしていた、なんていう話がありますが、それは仕組みとしてのダブルチェックが実施されていなかったからです。
 経営の世界では、「経理が休暇をとるのを拒否したら、気をつけろ」という鉄則があります。
 不正な経理をやって使い込みをしている人は、休暇中に他の人が経理を見ると不正がバレるので、休暇をとらなくなる習性があるからです。
 ただ、通常の会社は経理はダブルチェックを通すようにするのが一般的です。
  より大きな戦略的ミスの大半は、トップの「思い込み」によって起きます。
 「ここで飲食店をやれば儲かるはずだ」という思い込みから飲食店を始め、失敗するのと同じです。
 「思い込みのないトップを経営者にすればいい」というほど、コトは単純ではありません。
 どんな人間も、「思い込み」や「先入観」を捨てることはできないからです。
 プログラマーの考え方はいつもこうです。
 「配られたカードで勝率を最大化するにはどうすればいいか」
 したがって「思い込み」も戦術的ミスと同様、無くすことはできません。
 つまり配られたカードに弱いカードが1枚入っていたら、それでも勝てる手を考えなければならないのです。これが経営者です。
 経営判断から先入観や思い込みを排除する方法は、いろいろな人がいろいろなやり方を試しています。
 私が採用しているのは、社内限定の匿名チャットを用いる方法です。
 こうすると立場を超えて別け隔てなく意見を吸い上げやすくなります。
 経営者になると、耳に厳しい意見を言ってくれる人はぐんと減ります。
 そういうときに、フラットな目線でアドバイスをくれる人はとても大切なのです。

メールマガジン購読WirelessWire News メールマガジン

おすすめ記事を配信する『WirelessWire Weekly』と、閲覧履歴を元にあなたに合った記事をお送りする『Your Wire』をお送りします(共に週1回)
配信内容を詳しく見る

清水 亮(しみず・りょう)

1976年新潟県長岡市うまれ。6歳の頃からプログラミングを始め、16歳で3DCGライブラリを開発、以後、リアルタイム3DCG技術者としてのキャリアを歩むが、21歳より米MicrosoftにてDirectXの仕事に携わった後、99年、ドワンゴで携帯電話事業を立上げる。'03年より独立し、現職。'05年独立行政法人IPAより天才プログラマーとして認定される。

RELATED NEWS