1940 年代後半、Maurice Wilkes 氏率いるチームは、初期のコンピューターである EDSAC を開発しました。彼は 1949 年に、コンピューターでの作業中にあることに気づき、手記に残しています。当時、EDSAC は彼の勤務先の最上階にあり、テープ パンチャーや編集機器は 1 つ下の階に置かれていました。
「いつものように、EDSAC が置かれている部屋と編集機器の部屋を往復しているときのことでした。ふと、”階段の曲がり角で二の足を踏む” ような気持ちが鮮明に浮かんだのです。残りの人生のほとんどを、自分で書いたプログラムのエラーを探して過ごすことになるのではないかと」— Maurice Wilkes 著、『Memoirs of a Computer Pioneer (ウィルクス自伝 - コンピュータのパイオニアの回想)』、1985 年
つまり、プログラミングの誕生と同時にデバッグも生まれていたのです。継続的インテグレーションや有効なテレメトリなどの手法は、問題を見つけ出す役には立ちます。しかし、問題が表面化した際には、なぜそのような問題が起きたのかを自分自身で正確に突き止めなければなりません。
問題の原因は、自分や同僚の書いたコードにあることも、依存関係のあるライブラリやインフラストラクチャにあることもあります。往々にして、問題はソフトウェア スタックやハードウェア スタック全体で行われるさまざまなインタラクションから生じるため、わかりにくくとらえがたいものです。
問題レポートやスタック トレースなどで症状を見ても、原因にたどりつくことは困難です。この究明方法は体系立てられておらず、実地形式で学ぶムラのあるやり方になりがちです。しかし、他のあらゆるテクノロジーと同様に、素早く効果的にデバッグを行う方法は誰でも身につけられるものです。
方法
デバッグのやり方としては、症状から遡って一定の手順を繰り返す方法や、システムの動作が始まる “頂点” から取り掛かり、コードをたどって問題をモデル化する方法などがあります。
どのようなアプローチでも、方法自体は次のように系統立てられたものです。
- 発生した事実を確認する
- エラー発生時のシステムの状態を推測する
- 何が起きたかの仮説を立てる
- 既存のテレメトリや新しいコードを用いて、一度に 1 つの要素を変えながら仮説を検証する
- エラーが見つかるまでプロセスを繰り返す
実例
上記の手法について、エンジニアリング チームでのデバッグに関する話し合いの中で、Marc が次のような素晴らしい例を紹介してくれました。
Marc は先週、RabbitMQ の奇妙な問題のデバッグに取り組んでいました。問題は、あるクラスタを移行する過程でメッセージが配信されなくなるというものでした。
- 事実: メッセージが配信されていない。
- 仮説: メッセージは送信されなかった。
彼はログを調べ、メッセージ送信の直前の行を確認しましたが、メッセージの送信でエラーや例外が発生した形跡は認められませんでした。
- 事実: メッセージはキューに送信された。
- 仮説: メッセージは送信されたが配信されなかった。
そのため、キューにエクスチェンジが接続されていないのではないかと考え、RabbitMQ UI を開き設定を調べました。新しい RabbitMQ クラスタでのキューのバインドは適切なようでしたが、移行前のクラスタとは動作が異なっていました。
- 事実: キューはエクスチェンジにバインドされている。
- 仮説: 2 つのクラスタのトポロジにはなんらかの違いがある。
RabbitMQ の Web UI で移行前後のクラスタそれぞれをタブで開き、2 つのタブを見比べて違いを確認しました。その結果、ルーティング キーに差異のあることがわかりました。
- 事実: 移行前のクラスタではキュー バインド用にルーティング キーが設定されていたが、移行後のクラスタでは設定されていなかった。古いキューのバインドは手動で設定していたが、新しいバインドは構成管理から設定していた。
- 仮説: 構成管理コードでバインドの設定が適切に行われていない。
この仮説は容易に確認して修正できるものでした。
陥りやすいミス
事実だけに基づいて仮説を立てられるようになるには、練習が必要です。よくあるミスは、X が発生したなら Y は真だと証拠もなく思い込み、”X かつ Y” という観点で仮説を立ててしまうことです。
たとえば、一連のタスクが進行しない場合に、問題は容量にあると仮定してすぐにスケールアップするとしましょう。しかし、問題の原因が、タスク間で共有されているキューで競合が発生していることにあれば、スケールアップは事態を悪化させることにしかなりません。なによりもまず、競合が発生しているのではないかという推測をし、確かめるべきです。
一方で、経験を積めば仮説と検証の過程をある程度省略し、短時間で答えにたどり着けるようになります。重要なのはバランスです。経験に基づいて推測することは有効ですが、明確な推測を立てて検証を行いながらデバッグを進めることも肝要です。
再現性の低い問題などでは特に、デバッグ時に仮説を検証するための情報がないこともあります。その場合は、次回の問題発生時に有用な情報を収集できるように、新しいツールやテレメトリを導入することをお勧めします。
メモをとる
調査が長期にわたる場合には特に、デバッグの過程をメモすると役に立ちます。
- 多くのデータを集めてシステムのメンタル モデルの空白を埋めたら、最初のころの仮説を振り返ることで、不適切な推測を見つけ、新しいアイデアを生み出すことができます。仮説を立てた次の日に、新たな視点で眺め直すことも有効です。
- わかりにくいものをわかりやすく説明しようとしてみると、うわべのささいなことではなく、実はわかっていなかった部分が見えてきます。
- 共有用のメモを取ることで、他のメンバーも作業の進捗をよく把握できるようになります。デバッグはプログラミングと同じで、チームとして取り組むと最も成果が出やすくなります。
腕を磨く
他のスキルと同様に、デバッグをうまくできるようになるには、細心の注意と、方法の見直しと、他者との学習が必要です。陥りやすいミスに注意して上述の方法を試し、成果を同僚や友人に広めましょう。本記事の背景や、他のデバッグのアイデアについては、以下の関連資料をご覧ください。
デバッグは、ソフトウェア エンジニアにとって最も基本的なスキルです。日常的な問題と同じように、デバッグのスキルを磨こうとあらゆる努力を試みることで、着実に成長できるでしょう。
関連資料
- 『The Discovery of Debugging』(Brian Hayes 著): Maurice Wilkes 氏と EDSAC に関するすばらしいエッセイ
- 『What does debugging a program look like?』および『So you want to be a wizard』 (ともに Julia Evans 著): 参考になるアイデアや資料を紹介
- 『Effective Mental Models for Code and Systems』 (Cindy Sridharan 著): プログラマーが組み立てるメンタル モデルについて、膨大な参考資料を基に綿密に検討した記事
- 『Computers can be understood』および『Systems that defy detailed understanding (続編)』 (Nelson Elhage 著): 複雑な大規模システムのモデル化と検討の際に陥りやすいミスをまとめた記事