chromeデベロッパーツールでjavascriptのデバッグをする -node.jsもあるでよ-
ソースコードリーディングとかしてると、ただコード読んでてもどうしようもなく、オブジェクトの中身や変数などを見るためにデバッグツールを使いながらでないとやっていけないことが今になって分かりました。自分でもどうしようもなくアホだと思いながら戒めのために覚書。
デバッグツールの機能
僕自身まともに触れる言語はjavascriptとphpくらいなもので、どちらもeclipseのようなIDEを使わず頑なにvimを使って組んできました。phpの場合はxdebugと連携させる方法*1や、javascriptならrhinoなんかを入れてquickrunとかって方法も考えられますが、僕はある程度は知っていながらもひたすら標準のスタックトレースやalert,console.log,console.dirばかりしていたので、まずはIDEなどに搭載されている一般的なデバッグ機能を復習をかねて覚書。
ブレークポイント
ブレークポイント(英: breakpoint)は、ソフトウェア開発のデバッグ作業において実行中のプログラムを意図的に一時停止させる箇所である。ブレークポイントの指定機能は多くのデバッガに備えられており、これを用いることでプログラムの任意箇所への到達を自動的に捕捉できる。ブレークポイントでの停止後、プログラマは通常のデバッグ作業同様に実行環境(メモリ、レジスタ、ログ、ファイルなど)を観察し、プログラムが期待通りに機能しているかどうかを判断する。
との事。自分の期待しない動作をするときなど、一時停止してそのときの変数やプログラムの状態を確認。
どうでもいいですが、なんでブレークポイントを「張る(貼る?)」という表現するんでしょうね。
ステップイン、ステップオーバー、ステップアウト
デバッグ時はプログラムを1行または複数行ごとに手動で実行することができます。プログラムの最初から1行ずつというのではなく、ブレークポイントを張った場所で一旦停止し、それから各単位での実行になります。
その実行単位は以下の3つのような呼び方をされます。
- ステップイン
- 1行ごとに実行する。
- ステップオーバー
- 1行ごとに実行する。ただし関数があった場合はそれを1行とみなして実行する。
- ステップアウト
- 実行している関数が元に戻るまで実行する。
以下のようなコードを考えます。
function increment(arg){ var increment = 1, ret; // set breakpoint of StepOut ret = arg + increment; return ret; } var number = increment(1); // set breakpoint of StepIn,StepOver alert(number);
numberという変数の宣言をしている行にブレークポイントを設定したとします。ここでステップインをすると、1行ごとの実行を行うために次に実行されるのはincrementという関数内のincrementとretの変数宣言部になります。
ステップオーバーの場合は関数の内部に入らないため、次のalertが実行されることになります。
また、関数increment内でブレークポイントをはっている場合、ここでステップアウトを行うと、1行ずつの実行はせずに関数の最後まで実行するため、これまた次のalertが実行されます。
chromeデベロッパーツール
こっからが本題ですが、chromeのデバッグツールを使いこなそうというわけです。
windowsまたはlinuxの場合はCtrl+Shift+i、オシャレ気取りども御用達のmacの場合はCommand+Option+iで起動します。またページ内を適当に右クリックして「要素を検証」を押してもデベロッパーツールは起動します。
パネルがたくさんあると思うのですが、今回主に使うのは[Scripts]パネルなので、それについてだけ説明。
左ペインに解析中のスクリプトが表示されます。1の「test.js」と表示されている部分をクリックすると、同じくそのページで読み込まれているスクリプトを選ぶことができます。
スクリプトの左端に行数が表示されていますが、行数をつぶして赤い矢印の出ている部分が今実行した行です。また青い矢印は設定されたブレークポイントをさします。
設定したい行を左クリックでブレークポイントが設定でき、再度クリックすればブレークポイントをはずすことができます。また、右クリックで条件付ブレークポイントの設定もできます。
右ペインは実行時の状況が表示されます。
一番上にはステップ実行のコマンドボタンが表示されます。以下のボタンのように割り当てられています。
Watch Expressions
「WatchExpressions」は任意の式や変数を入力しておけば、その内容が確認できます。+ボタンを押せば入力ボックスが出てくるので、その中に変数名などを入力します。例えば「this」を入れておけばステップ実行時に各行でthisは何にバインドされているか確認することができます。thisが面倒なjavascriptでこれはすごく便利。
Call Stack
その名の通りコールスタックが見えます。関数内でさらに関数やメソッドを呼んだ場合に、スタックに実行途中の関数などの情報が積まれます。それらが全て見えるようになるわけです。表示されている各関数をクリックすれば、その関数内におけるScopeVariables(後述)やwatchExpressionで設定したthisの値が確認できます。
Scope Variables
実行行からアクセスできる変数スコープが見えます。グローバルスコープ(windowオブジェクト)やローカルスコープ、またクロージャを保持していればクロージャの中も見えます。まる見えです。
ブレークポイント
Breakpoints以下は設定されているブレークポイントが見れます。
「Breakpoint」には上述の方法で設定したブレークポイントをどこにはったかという情報が見えます。
で、僕が感動したのはここからの機能です。
手動では設定できないような面倒な部分のブレークポイント設定がサポートされているのです。
DOM Breakpoints
ページに表示されるある要素を右クリックし、「要素を検証」を押します。そうするとデベロッパーツールの[Elementsタブ]が開かれ、さっきクリックした要素のHTMLがハイライトされます。ここでさらにハイライトされている部分を右クリックすると
- Break on Subtree modifications
- Break on Attribute modifications
- Break on node removal
という選択肢が表示されます。
これらを選ぶことで、その要素ツリーや属性の変更時・ノード削除時にブレークポイントを設定することができるわけです。これはなかなかすごい。
実際に試してみるのが早いのでしょうが、はてダじゃサンプルスクリプトを実行できないので、chromeデベロッパーツール公式ページで試してみましょう。
XHR Breakpoints
XHRでsendを使った瞬間にブレークさせることができます。これもchromeデベロッパーツール公式ページで試してみてください。
「XHR Breakpoints」の横にある+ボタンを押してurlを入力すると、そのURLと通信した際にブレークします。
Event Listener Breakpoints
イベントのタイプが並んでいるのですが、チェックを入れたイベントが発生した際にブレークします。
これもchromeデベロッパーツール公式ページで試すと良いです。
console
ちょっとブレークポイントを離れてコンソールを動かしてみましょう。
[Console]タブでコンソールを動かすこともできるのですが、デベロッパーツール下部の変なボタンを押してもコンソールを出すことができます。後者の場合は[script]タブと同時に表示できるのでおすすめです。
コンソールはブレークしてスクリプトを停止している最中でも値の代入か関数の実行を行えます。補完機能が働くのですごく使いやすいです。
また以下のような特殊関数をコンソール内では使用できます。
$ document.getElementByIdのショートカット $$ document.querySelectorAllのショートカット $x document.evaluate(XPath)のショートカット $1, $2, $3, $4 $1から$4にはコンソールでの実行結果が4回分だけ記憶されています。 copy(text) 引数に渡した文字列をクリップボードにコピー dir(object) 引数に渡したオブジェクトを解析(DOM要素を渡したときもオブジェクトとして扱う) dirxml 引数に渡したノードをツリー表示 inspect 引数に渡したオブジェクトに応じて適切に解析を行う(localStorageを渡すとStorageパネルに切り替えるなどの処理も行う)。 keys オブジェクトのプロパティを配列で返す monitorEvents 引数に渡したDOMについて各種イベントを監視する。第2引数で監視するイベントの種類を制御できる。 unmonitorEvents monitorEventsを解除 profile JavaScriptのプロファイリングを開始する profileEnd JavaScriptのプロファイリングを終了する values オブジェクトが持つ値を配列で返す
参考:http://dl.dropbox.com/u/148989/slide/console.html
[http://code.google.com/intl/ja/chrome/devtools/docs/overview.html:title=http://code.google.com/intl/ja/chrome/devtools/docs/overview.html
]
組み込みデバッガを使う
node.jsは特にモジュールを追加することもなく、最初から組み込みのデバッガが入っています。基本コンソールからの操作になりますが、それでもブレークポイントはったりステップ実行したりと基本的なことはできるようです。
以下のようにdebug引数を指定してnodeを起動するとデバッグクライアントが起動します。
$ node debug test.js
これでデバッグプロンプトが表示されるようになり、デバッグ用コマンドの入力ができるようになります。
デバッグ用のコマンドについてはデバッグプロンプトでhelpと入力したり、公式のドキュメントで確認できます。
node-inspector
でもやっぱりCUIでのデバッグは見づらいし分かり難くて面倒なわけです。
その面倒さを解決してくれるのがnode-inspectorなのです。
node.jsもchromeもjavascriptエンジンはV8ですし、chromeのデベロッパーツールの見た目はjavascriptとcssで作られているようなので、それをうまいこと組み合わせて、上で散々紹介したデベロッパーツールでnode.jsのデバッグも行えるようにしたのがnode-inspectorなのです。これこそが僕がchromeをjavascriptのデバッグに使いたかった理由です。*2
というわけでnode-inspectorを使ってみましょう。
まずはnpmでインストールします。
$ npm install -g node-inspector
インストールができたらnode-inspectorをバックグラウンド起動させます。
$ node-inspector &
visit http://0.0.0.0:8080/debug?port=5858 to start debugging
これでvisit云々と書いてあるアドレスにchromeでアクセスすればnode-inspectorが動いているのが分かります。
次に適当にテスト用のコード(test.jsとします)を作ってみます。
var http = require('http'); http.createServer(function (req, res) { console.log("1"); res.writeHead(200, {'Content-Type': 'text/plain'}); req.on("end", function(){ console.log("2"); console.log("3"); res.write("debugging node-inspector"); res.end(); }); }).listen(1337, "127.0.0.1"); console.log('Server running at http://127.0.0.1:1337/');
これをデバッグモードで動かしてみましょう。
$ node --debug test.js
さっき動かしたnode-inspectorの画面に行って[Script]タブを選択し、test.jsを表示させましょう。[Script]タブを開いたときはhttp.jsなどの組み込みモジュールが表示されているので、スクリプト名の表示されているボタンを押してtest.jsを選択します。
test.jsが表示できたら、好きなところでブレークポイントを設定します。console.logにでも設定してみましょう。
ブレークポイントを設定した状態で、新たにタブを開いてtest.jsがlistenしているhttp://127.0.0.1:1337/にアクセスしてみます。
アクセスした段階では debugging node-inspector という文字は表示されていません。
これはさっきはったブレークポイントで一時停止しているためです。元々開いていたnode-inspectorのタブに戻ると、test.jsのステップ実行ができる状態になっているはずです。
参考:
https://github.com/dannycoates/node-inspector
http://d.hatena.ne.jp/replication/20111202/1322752174