「論より証拠」「推測より計測」…実動作を確認しながらのデバッグ術


ある程度の大きさを持ったプログラムの場合、「書いて一発で問題なく動く」ということは、ほぼ期待できません。ということで、開発の大半は動作検証とデバッグということになりますが、デバッグに当たっていくつか知っておくと役に立つことがあります。

エラーメッセージは情報の宝庫

ときどき、「エラーが出ると一目散にコードを開いて、とりあえず眺める」という人がいるようですが、これは無駄が多すぎます。エラーメッセージには多くの場合、

  • エラーが起きたファイルと、その行数
  • どのようなエラーが起きたか
  • スタックトレース(エラーが起きるコードが、どこから呼び出されたか)

などといった情報が含まれています。これをスタートラインとしましょう。

なお、エラーメッセージについてはどれだけ出すかの設定が可能です。たとえばPHPでは、error_reporting(-1);とすればすべてのエラーを表示する設定になります1。逆に本番環境では、表に出しては攻撃者にとって格好の手がかりとなるので、エラーは表示させないようにしましょう。

メッセージから辿る

書いてある行自体にエラーの原因があることもありますが、そうでないケースももちろん多いです。まず、文法エラーの場合は、エラーに「気づいた」行でエラーとなるので、その前後の行にミスがあることもあります。また、その行で意図して例外を投げている場合は、「与えた引数がおかしい」、あるいは「外部環境にエラーがある」などの状況ということもあります2

実環境でのエラーログ

実環境でもエラーは起きうるので、(表示はともかく)内部的にログを取っていくことが重要となります。Railsのexception_notificationのように、メールなり社内チャットなりに流す仕組みを作っておくといいでしょう。ときどきは生ログもチェックして、不自然な動作がないか確認することも必要です。

デバッグ術

エラーが出なくても、うまく動作しない、ということも多いものです。そのような場合にも、いくつか定石のようなものがあります。

デバッグツールを活用する

デバッグツールが整っている環境であれば、それを使って変数のウォッチやブレークポイントの設置など、便利に進められます。IDEでもデバッグ機能がある例は多いですし、ブラウザのJavaScriptであればF12キーで開発ツールを開けます。Railsではweb-consoleがあって、そのページにロードされた変数を使えるコンソール環境が、Web内で動かせます。

要素ごとに分けてチェックする

複雑に絡まったものをまとめてチェックしても、動いているか動いていないかしかわかりません。たとえば、JavaScriptからAjaxでPHPにリクエストを投げて、返り値で処理をする場合、

  1. JavaScriptで送信する値を集計する
  2. Ajax送信する
  3. PHP側で値を受信する
  4. 受信した値をPHP側で処理して、結果を返す
  5. JavaScriptで結果を受信する
  6. 受信した値を元に、処理を行う

のようなステップに分かれています。まずはF12でブラウザの開発ツールを開けば、リクエストの送受信状況が見られますので、「そもそもリクエストが飛んでいない」とか「PHPが500 Internal Server Errorを返している」とか、状況の切り分けができます。

デバッグ用にもコードを書き換える

「ここの値をとりあえず確認したい」、あるいは「とりあえずここまでの動きを確認したい」というような場合、一時的にコードを書き換えるような方法が有効です。Gitなどでバージョン管理された環境なら、いくら変えても簡単に元に戻せます。

速度チューニング

こちらについては以前にまとめたものがありますが、まずは「計測」です。思わぬところが速度的に足を引っ張るということも、よくあります。たとえば、もともと1ミリ秒しかかかっていない処理は、どれだけ速くしたところで、1ミリ秒以上速くなることはありません。逆に、100ミリ秒かかる処理を5%高速化できるなら、そっちのほうが全体への効果としては大きくなります。

重大な例外

1つだけ気をつけて欲しいのは、仕様の範囲外の動作についてです。たとえば、JavaScriptでのソートは安定性が保証されていませんし、C言語ではあちこちに「未定義の動作」という怪物が現れます。このような場面では、仕様の範囲外の挙動を可能な限り回避するようにコードを書きましょう。「仕様にない」ということは、「何かの気まぐれで将来的に変わるかもしれない」ということと同義なので、現行の環境で動いても、それは「たまたま」に過ぎないものです。


  1. 軽度のE_NOTICEまで表示するようになりますが、(既存のコードでE_NOTICEが出まくるものを流用するような状況なら別として)変数名間違いを報告してくれるなど有用なので、E_NOTICEも出さないコードで書くことを、強く推奨します。 

  2. たまに、先に空っぽの関数を作っておくような場合に、「例外を投げる行だけ書いておく」なんてこともあります。