袋小路です! 何時間も費やしていろいろ試してみたけれどもうまくいきません。コードをじっと吟味してもエラーになりそうなところはありません。2、3回ロジックを見直して、何度も実行しています。単体テストも助けにはならず、同じく失敗してしまいます。もはやどうしていいか分からず、虚空を見つめたくなります。ひとり闇の中にいるように感じて、だんだん腹が立ってきます。
こんなときの自然な反応は、コードの品質を落とし、邪魔なものを全部捨て去ることです。コードのあちこちにprintをちりばめて、なにかうまくいくことを祈るわけです。これでは暗闇で的を狙うようなもので、望み薄なことが分かるでしょう。
よくある話だと感じたのではないでしょうか。今までに数行以上のJavaScriptを書いたことがあるなら、この暗闇を体験しているかもしれません。恐ろしいプログラムによって、虚空に置き去りにされることがあるはずです。ある時点から、原始的なツールと技術でこの脅威に立ち向かうのは賢くないと分かってきます。気を付けていないと、小さなバグの特定に何時間も費やしてしまうのです。
良い対処方法は、優れたツールを使いこなすことです。優れたデバッガーは試行錯誤のループを短縮し、作業を生産的にしてくれます。幸いなことに、Nodeには最初から優秀なツールが備わっています。Nodeのデバッガーは万能で、どのようなJavaScriptのコードにも適用できます。
記事で紹介するのは、JavaScriptのコーディングにおいて貴重な時間の浪費から救ってくれる戦略です。
Node CLIデバッガー
Nodeコマンドラインデバッガー(Node CLIデバッガー)は便利なツールです。これまで苦境に陥った際、なにかが原因でモダンなエディタが使えず困ったことがあるなら役に立つでしょう。Node CLIデバッガーはTCPベースのプロトコルでデバッグ対象のデバッグをするものです。コマンドラインクライアントはポートから実行中のプロセスへアクセスしてデバッグセッションを開始します。
Node CLIデバッガーはnode debug myScript.jsコマンドで開始しますが、debugフラグを入れたことに注目してください。以下は、確実に覚えてほしいコマンドです。
- sb('myScript.js',1):スクリプトの最初の行にブレイクポイントを付加する
- c:停止中のプロセスを再開し、ブレイクポイントに達するまで実行する
- repl:評価のためデバッガーのRead-Eval-Print-Loop(REPL)を開く
エントリーポイントは気にしなくてよい
最初のブレイクポイントをセットする際、必ずしもエントリーポイントに置く必要はありません。たとえば、myScript.jsはmyOtherScript.jsを読み込みます。デバッグツールでは、エントリーポイントではないmyOtherScript.jsにブレイクポイントをセットできます。
次のような例です。
// myScript.js
var otherScript = require('./myOtherScript');
var aDuck = otherScript();
次のスクリプトは別の例です。
// myOtherScript.js
module.exports = function myOtherScript() {
var dabbler = {
name: 'Dabbler',
attributes: [
{ inSeaWater: false },
{ canDive: false }
]
};
return dabbler;
};
もしエントリーポイントがmyScript.jsでも心配無用です。たとえば、ブレイクポイントはsb('myOtherScript.js',10)のようにセットできます。デバッガーは、モジュールがエントリーポイントでなくても問題にしません。ブレイクポイントを正しくセットしているのなら、警告が表示されても無視してください。このときNodeデバッガーは、モジュールがまだ読み込まれていないと警告してくるかもしれません。
アヒルのデモの時間
それではデモの時間です。例として以下のプログラムのデバッグをします。
function getAllDucks() {
var ducks = { types: [
{
name: 'Dabbler',
attributes: [
{ inSeaWater: false },
{ canDive: false }
]
},
{
name: 'Eider',
attributes: [
{ inSeaWater: true },
{ canDive: true }
]
} ] };
return ducks;
}
getAllDucks();
デバッグするにはコマンドラインツールで以下のようにコマンドを入力します。
> node debug debuggingFun.js
> sb(18)
> c
> repl
上のコマンドで、コードを順番に調べられます。例として、REPL(Read-Eval-Print-Loop)でアヒルのリストをデバッグするとします。アヒルのリストが返される部分にブレイクポイントを置くと、次のように表示されるでしょう。
> ducks
{ types:
[ { name: 'Dabbler', attributes: [Object] },
{ name: 'Eider', attributes: [Object] } ] }
各アヒルの属性が表示されていません。その理由は、オブジェクトが何重もの入れ子になっている場合、REPLでは浅い階層しか調べないからです。コードの海を探索する際は注意してください。深すぎるコレクションは避けることも考えます。いくつかに分解して変数を割り当ててください。たとえば、コード内でducks.types[0]を別の変数に割り当てるのです。あとで時間に追われたときに感謝するときが来るでしょう。
たとえば、次のようにします。
var dabbler = {
name: 'Dabbler',
attributes: [
{ inSeaWater: false },
{ canDive: false }
]
};
// ...
var ducks = { types: [
dabbler,
// ...
] };
クライアントサイドのデバッグ
同じくこのNodeのツールはクライアント側のJavaScriptのデバッグもできます。理論的には、NodeデバッガーはV8 JavaScriptエンジンで動作します。デバッガーは昔ながらの素のJavaScriptに対しても普通に機能します。特殊なワザなどはなく、ただツールを上手に使いこなすだけです。Nodeデバッガーは1つの仕事をきっちりこなしてくれるので、最大限活用してください。
次の簡単なデモを見てください。
アヒルのいずれか、たとえばEider(カモの一種)をクリックするとそれがフェードアウトします。もう一度クリックすると再び現れます。粋なDOM操作です。それではどのようにしてこのコードを、先程と同じサーバーサイドのNodeツールでデバッグするのでしょうか。
この動作を実現しているモジュールは次のようになっています。
// duckImageView.js
var DuckImageView = function DuckImageView() {
};
DuckImageView.prototype. onClick(e) {
var target = e.currentTarget;
target.className = target.className === 'fadeOut' ? '' : 'fadeOut';
};
// The browser will ignore this
if (typeof module === 'object') {
module.exports = DuckImageView;
}
Nodeでデバッグが可能になる仕組み
Nodeプログラムは上のコードを使えます。たとえば、次のようになります。
var assert = require('assert');
var DuckImageView = require('./duckImageView');
var event = { currentTarget: { } };
var view = new DuckImageView();
view.onClick(event);
var element = event.currentTarget;
assert.equal(element.className, 'fadeOut', 'Add fadeOut class in element');
JavaScriptがDOMと固く結びついているかぎり、どこでもデバッグできます。Nodeのツールはコードがクライアントサイドにあることなど問題にせず実行できます。自分のモジュールを作る際もデバッグ可能な書き方を検討してください。暗闇から救い出す、過激かつ新しい手段になり得ます。
これまでに虚空を見つめる時間を過ごしたことがあるならば、ブラウザーでJavaScriptをリロードするのがどれだけ苦痛か知っているでしょう。コードの変更後のブラウザーのリロードは恐怖です。リロードのたびになにか別の問題でさらに時間を費やす可能性をはらんでいるからです。たとえば、データベースやキャッシュをふいにするなどです。
良い方法は、JavaScriptを書くときに自由度をより高くしておくことです。そうすれば、簡単かつスタイリッシュにいやらしいバグをつぶせます。目指すのは目の前のタスクに集中し、楽しくかつ生産的になることです。ソフトウェアのコンポーネントを切り離し、リスクを減らします。健全なプログラミングとは問題を解決することのみならず、自ら問題を招かないようにすることでもあります。
エディターでのデバッグ
コマンドラインからのデバッグはかなり出来が良いものの、大半の開発者はコマンドラインでコーディングはしません。少なくとも私は大半の時間をコードエディターと共に過ごすほうが好きです。同じNodeのツールがコードエディターでも活用できたらよいと思いませんか。必要なときに必要なツールが使えるようになるべきですから、エディターでも使えるようにしたいものです。
現在たくさんのエディターが出回っており、この記事ですべてをカバーしきれません。ツールは、デバッグが使いやすくて簡単なものを選びたいものです。もし行き詰まったときにはショートカットキー一発でデバッガーを起動できることはとても大切です。コードを書く際に、コンピューターがコードをどのように評価するかを知っておくことは重要です。私から見て、JavaScriptのデバッグツールとして傑出したエディターがあります。
Visual Studio Codeは、JavaScriptのデバッグのために推薦したいツールです。コマンドラインツールと同じデバッグプロトコルを使っています。ショートカットキー(Windows/MacともにF5キー)やインスペクションなど、デバッガーに期待されるすべての機能がそろっています。
すでにVS Codeをインストールしていてもデバッガーを使ったことがないなら、注目してください。左のデバッグタブをクリックし、ギア型のボタンをクリックします。
launch.jsonファイルが開くので、デバッグエントリーポイントを設定できます。たとえば、次のようになります。
{
"type": "node",
"request": "launch",
"name": "JavaScript Tests",
"program": "${workspaceRoot}\\entryPoint.js",
// Point this to the same folder as the entry point
"cwd": "${workspaceRoot}"
}
高いレベルからVS Codeに、なにをどこで実行するか指示するのです。このツールはnpmとNode両方のエントリーポイントに対応しています。
セットアップできたら、ブレイクポイントを置き、ショートカットキーを叩きます。
Nodeのデバッガーでは、変数を検証したりコードを順次調べられます。変更があったとき、コードがどうなっているかが明示されます。すべては、いやらしいバグをつぶすためです。
たとえVS Codeを選択しなくても、同じ原則が一律に当てはまります。ツールに対して、なにをどこで実行するかを指示するのです。ブレイクポイントを置き、ショートカットキーを叩き、デバッグセッションに入るのです。
変換コンパイルされたJavaScriptのデバッグ
Nodeのツールはnpmパッケージを通じて変換コンパイル(トランスパイル)されたJavaScriptにも対応します。どの言語にもそれぞれのツールがあります。各言語における約束事も異なっています。たとえば、TypeScriptはほかのトランスパイラーと異なるデバッグツールがあります。トランスパイラー使用時のNodeのデバッグは、結局、フレームワークとツールの選択次第です。
選ぶツールは、ワークフローに統合できるものをおすすめします。変更箇所から極力離れずに、短時間で試行を繰り返せるようにします。ぜひ1秒以下でさっとブレイクポイントを設置できるようになってください。
エディターにある、デバッグのためソースマップ機能の使用も検討してください。
試行錯誤のサイクルが高速でなければ痛い目に遭うだけです。ツールを選ぶときは、健全なデバッグを妨げないものを選んでください。
最後に
デバッグにconsole.log()を使うのは、とにかくやめたほうが良いでしょう。この方法を使った結果、たびたびパニックに陥りました。まるで闇の中で的を狙うようなものです。
そのとおり、闇雲に打ちまくれば跳ね返りで自分が傷つくか、味方を誤射してしまいかねません。コードベースにロギング用のコードをちりばめれば自分自身、あるいは引き継いだ人は混乱するでしょう。直前のコミットからデバッグしてもなにも分からず、ただ煙に巻かれるだけです。またJavaScriptは変数が存在しない場合は例外を投げますので、さらに混乱に拍車がかかります。
ときに開発者は自分の目で見てプログラムを実行できるような錯覚に陥ります。哲学的に言うなら、人間の目は嘘をつくので、見えるものが真実とは限りません。視覚は頼りなく、自分が信じたいものを見せるので、開発者はますます迷い込むでしょう。
優れたデバッガーは、コンピュータがプログラムをどのように実行しているのかを見せてくれます。これによりコンピュータが理解しやすい、より良いソフトが書けるようになります。Nodeのデバッガーなら変更を検証でき、どうにかなるさという思い込みを排除できます。優秀なプログラマーなら、ぜひ使いこなしたいツールです。
(原文:Debugging JavaScript with the Node Debugger)
[翻訳:西尾健史/編集:Livit]