mizdra's blog

ぽよぐらみんぐ

JavaScript で print デバッグ時に変数名を出力する

数列の和を求めるプログラムを作成することになり、意気揚々と以下のようなプログラムを書いたという状況を想像して下さい。

function sum(nums, acc = 0) {
  if (nums.length === 0) return 0;
  if (nums.length === 1) return nums[0];
  return sum(nums.slice(1), acc + nums[0]);
}

const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log(sum(nums)); // expected: 55

一見すると何も問題なさそうに見えるプログラムですが、実はバグがあります (皆さん分かりますか?) *1。実際に上記プログラムを実行すると 55 ではなく 10 が出力されます。

こうした場面に遭遇すると、自然と sum 関数が呼び出される度に numsacc がどう変化していくかを知りたくなってきます。この時取りうるデバッグ方法には様々なものがありますが、最も簡単なのは print デバッグでしょう。

function sum(nums, acc = 0) {
  console.log(nums);
  console.log(acc);
  if (nums.length === 0) return 0;
  if (nums.length === 1) return nums[0];
  return sum(nums.slice(1), acc + nums[0]);
}

これを Chrome devtools の Console パネルで実行すると以下のように出力されます。出力がどう整形 (色が付いたり、クリックで折りたたみを展開できたり) されるかは、その JavaScript 実行環境 (Chrome や Firefox、Node.js など) によりますが、大体どの実行環境でも似たような見た目で出力されるはずです。

f:id:mizdra:20210502225505p:plain

複数行に渡る出力を1行にまとめたい

実は console.log は複数の引数を渡せるようになっていて、以下のように書くこともできます。一度に複数の値を渡すと、それらの値は一緒の行にまとめられて出力されるので、1回の呼び出しで複数行出力されて見にくいという不満を解消できます。便利ですね。

function sum(nums, acc = 0) {
  console.log(nums, acc);
  if (nums.length === 0) return 0;
  if (nums.length === 1) return nums[0];
  return sum(nums.slice(1), acc + nums[0]);
}

f:id:mizdra:20210502230145p:plain

ここまではよくある tips ですね。

変数名も出力したい

出力する変数がだんだん増えてくると、出力の何番目がどの変数だったのか判断が難しくなります。当然変数名も一緒に出力して、どの値がどの変数のものかがひと目で分かるようにしたくなってくると思います。

そうした状況でオススメなのが、次のテクニックです。

function sum(nums, acc = 0) {
  console.log({ nums, acc });
  if (nums.length === 0) return 0;
  if (nums.length === 1) return nums[0];
  return sum(nums.slice(1), acc + nums[0]);
}

f:id:mizdra:20210502230518p:plain

console.log の引数リストを { ... } で囲うだけです。これだけで変数名も一緒に出力されるようになります。簡単ですね。

変数名が出力される仕組み

「何でこれで変数名が出力されるの?」という疑問を持った方も居るともうので、軽く解説します。先程のコードは Shorthand property names という、オブジェクトを初期化するためのシュガーシンタックスです。ES2015 で導入された比較的新しい *2 記法です。以下の2つのコードは等価になります。

console.log({ nums, acc });
console.log({
  nums: nums,
  acc: acc,
});

つまり先程のコードは、変数 nums の値を nums という名のプロパティに、変数 acc の値を acc という名のプロパティに持つオブジェクトを console.log で出力していた訳ですね。変数名を出力していたというよりは、たまたま変数名と同じ名前のプロパティ名が出力されていて、変数名が出力されているように見えていた、という感じです。

別解: degugger; を仕込む

結構忘れられがちなのですが、専用のデバッガを使うという手もあります。変数名の表示はもちろん、スタックトレースの表示やステップ実行ができたりと、print デバッグよりもずっと高級なデバッグ体験が得られます。もし高級なデバッグ体験が欲しければこちらを使うのが良いでしょう。

デバッガを利用する最も手軽な方法は、 debugger 文です。変数の値を覗き見したい行に debugger; と書いて Chrome などで実行すると、その行に到達した瞬間にデバッガが自動で起動して、実行が一時停止します *3。後はデバッガのメニューから変数の状態を見たり、ステップ実行してみたりと好き放題できます。

f:id:mizdra:20210502234958p:plain

デバッガは利用するための環境構築が大変なイメージがあり、敬遠されがちですが、便利なのでスキを見て使っていけると良いと思っています。最近の VSCode でもエディタ組み込みのデバッガの開発に力を入れていたりと、どんどんデバッガの利用が容易になっているように感じています。debugger; くらいなら簡単に始められるので、是非使ってみてはいかがでしょうか。

おまけ1

実行環境がChrome devtools、かつ関数呼び出しの入力を監視したい時限定で、変数名を出力出来るわけではないけど、monitor 関数も便利だよと教えて頂いた。便利!!!

blog.pastak.net

おまけ2

サムネオチって言葉久々に聞いた気がする。

*1:解: 3行目で acc を足し忘れている

*2:といっても標準化されてから約6年経過してますが。

*3:何となく分かると思いますが、debugger; のある行に breakpoint を仕掛けたかのような挙動になります。

ポケットモンスター・ポケモン・Pokémon・は任天堂・クリーチャーズ・ゲームフリークの登録商標です.

当ブログは @mizdra 個人により運営されており, 株式会社ポケモン及びその関連会社とは一切関係ありません.