Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう。
Visual Studio Code(以下、VS Code)はプログラマー向けのテキストエディタだ。エディタではあるのだが、さまざまな機能も包含した極めて高機能のエディタである。そうした高度な機能の1つとしてデバッグ機能がある。本特集では、VS Codeを使ってデバッグを行うための基礎知識、手法、ノウハウを紹介していこう。なお、本稿ではWindows版のVS Code 1.14.1で動作を確認している。
VS Codeが組み込みでサポートしているのはJavaScriptアプリ(Node.jsランタイム)のデバッグ機能だ(TypeScriptなど最終的にJavaScriptコードにトランスパイルされる言語のデバッグも可能)。VS Code自体がElectron(JavaScript/HTML/CSSでデスクトップアプリを作成するためのフレームワーク)を用いて作られているのを考えると当然ともいえる。他の言語で記述したアプリのデバッグを行うには、そのための拡張機能が必要になる。
例えば、マイクロソフトが提供しているC#用の拡張機能には.NET CoreをランタイムとするC#コードのデバッグサポート機能が含まれている。この他にも多くの言語用にVS Codeでのデバッグを行えるようにするための拡張機能が提供されている。これらはVisual StudioのMarketplaceページで閲覧することも、VS Codeの[拡張機能]サイドバーで閲覧することもできる。
今回は取りあえず、VS Codeが組み込みでサポートしているJavaScriptコードのデバッグ機能を利用しながら、VS Codeのデバッグの基本を見ていこう。
VS Codeでデバッグを行うにはウィンドウ左端のアクティビティーバーで[デバッグ]アイコンをクリックする。すると、サイドバーにデバッグに関連する情報が表示されるようになる(これを[デバッグ]ビューと呼ぶ)。ここからデバッグの構成、デバッグの開始などが行える。また、デバッグ中は変数の値や関数の呼び出し階層(コールスタック)を表示したり、ブレークポイントの有効化/無効化を切り替えたりできる。
(1)の[デバッグ]アイコンをクリックすると、(2)の[デバッグ]ビューが表示される。(3)の[デバッグの開始]ボタンをクリックすると、(4)に示された構成でデバッグが開始される(上の画像はデバッグ用の構成が行われていない状態。このときに[デバッグの開始]ボタンをクリックすると、デフォルトの構成でデバッグが開始される)。(5)のボタンをクリックするとlaunch.jsonファイルが作成/表示され、デバッグの構成を行えるようになる。(6)の[デバッグ コンソール]ボタンをクリックすると、(7)のデバッグ用のパネルが表示される。[デバッグ]ビューにある[変数][ウォッチ式][コール スタック][ブレークポイント]の各ペーンについては後で必要に応じて説明をしていこう。
ここでは、「Dev Basics/Keyword Emotion API」で作成した、マイクロソフトのCognitive Servicesが提供しているFace APIとEmotion APIを利用するJavaScriptプログラムをデバッグ実行してみることにしよう。サンプルのコードなので、詳細については前掲のリンクを参照してほしい。簡単に説明しておくと、このプログラムはFace APIとEmotion APIを利用して、URLで指定した画像ファイル内の顔の表情を調べてコンソールに出力するものだ。なお、実際にコードを実行してみるにはAzureポータルからFace APIとEmotion APIのサブスクリプションを追加して、それらのAPIへのアクセスキーを取得し、定数f_key/e_keyにセットする必要がある。
また、このコードではFace API呼び出しをラップしたPromiseオブジェクトを返すgetFaceRectangle関数と、Emotion API呼び出しをラップしたPromiseオブジェクトを返すgetEmotion関数を作成し、それらの呼び出しをthenメソッドでチェーンさせている。
const request = require("request");
const f_key = "Face APIのアクセスキー";
const f_url = "https://westus.api.cognitive.microsoft.com/face/v1.0/detect";
const e_key = "Emotion APIのアクセスキー";
const e_url = "https://westus.api.cognitive.microsoft.com/emotion/v1.0/recognize";
const body = {
url: "https://insidernetpictures.blob.core.windows.net/pictures/sampleface.png"
};
var opt = {
headers: {
"Ocp-Apim-Subscription-Key": "",
"Content-Type": "application/json"
},
body: JSON.stringify(body)
};
function getFaceRectangle() {
return new Promise(function(resolve, reject) {
opt.url = f_url;
opt.headers["Ocp-Apim-Subscription-Key"] = f_key;
request.post(opt, (err, res, body) => {
const tmp = JSON.parse(body);
resolve(tmp[0].faceRectangle);
});
});
}
function getEmotion(rect) {
return new Promise(function(resolve, reject) {
opt.url = e_url+
`?faceRectangles=${rect.left},${rect.top},${rect.width},${rect.height}`;
opt.headers["Ocp-Apim-Subscription-Key"] = e_key;
request.post(opt, (err, res, body) => {
resolve(JSON.parse(body));
});
});
}
getFaceRectangle()
.then(rect => getEmotion(rect))
.then(data => console.log(data));
まずデバッグの構成を行わずにそのまま[デバッグの開始]ボタンをクリックして、デバッグしながら、VS Codeでのデバッグの基本を見てみよう。
上のコードをそのままデバッグ実行しても、出力結果が[デバッグ コンソール]パネルに表示されるだけなので、ブレークポイントを2つ設定して、プログラムの実行を途中で止めてみよう。ここでは以下に示す2箇所にブレークポイントを設定してみた。
ブレークポイントは上の画像の(1)で示した領域をクリックすることで、クリックした行に設定される。(2)に示す[デバッグ]ビューの[ブレークポイント]ペーンには、設定したブレークポイントが表示される。ここを見ると分かる通り、ブレークポイントの左にはチェックボックスがあり、これをオン/オフすることで、ブレークポイントの有効/無効を切り替えられる。その上に2つある[すべての例外]と[キャッチされない例外]は例外発生時にプログラムのデバッグ実行を中断させるかどうかの切り替えに使用できる。
ブレークポイントを設定したのは、Face APIの呼び出し結果を処理するコールバック関数内部のコードと、Emotion APIを呼び出す直前のコードの2箇所だ。どちらも関数が返すPromiseオブジェクトにラップされていて、さらに前者はそこから呼び出されるコールバック関数となっている。少々複雑な構造のコードであってもブレークポイントを設定できるのは便利なところだ。
なお、設定したブレークポイントを削除するには、エディタ画面で同じ位置をクリックするか、[ブレークポイント]ペーンで削除したいものを右クリックして、コンテキストメニューから[ブレークポイントの削除]を選択する。コンテキストメニューからは全てのブレークポイントを削除したり、全てのブレークポイントの有効/無効を切り替えたりもできる。
では、ブレークポイントを設定したので、[デバッグの開始]ボタンをクリックしてみよう。これにより、デバッグが開始され、VS Codeのウィンドウが次のような表示に切り替わる。
(1)に示したように、デバッグが中断した行は薄いクリーム色で示される。また、ウィンドウ上部にはデバッグ実行を進められるように(2)のツールバーが表示される。(3)の[デバッグ]ビューを見ると、さまざまな情報が表示されていることが分かる。(4)の[デバッグ コンソール]パネルにはデバッグ自体についての情報が表示されている(ここではNode.js 6.9.4を使用しているので、古いプロトコルでデバッグを開始した旨が表示されているのが分かる。Node.jsのバージョンによっては、Node.js 6.3以降でサポートされているInspectorプロトコルを利用したデバッグも可能だが、これについては後述する)。
デバッグ実行を進める前に、それぞれについて少し詳しく見ていこう。
デバッグで中断した行とその周辺にマウスカーソルを移動させると、変数やパラメーターの値がポップアップ表示される。「ちょっとこの値を知りたい」といった場合には、これを使うのが便利だ。例えば、以下は「const tmp = JSON.parse(body);」行の「body」にマウスカーソルを合わせたところだ。
また、識別子やコードの一部をダブルクリックしたり範囲選択したりして、それを右クリックすると、コンテキストメニューからさまざまな操作を選択できる。
コンテキストメニューから[デバッグ: 評価]を選択すると、選択された範囲の式の値が評価されて[デバッグ コンソール]パネルにその値が表示される。[デバッグ: ウォッチに追加]を選択すると、それが[デバッグ]ビューの[ウォッチ式]ペーンに追加される。
[列ブレークポイントの追加]というのは、本稿で見ているような「キレイ」なコードではあまり意味がないが、ミニファイされて1行に複数のステートメントが含まれているコードをデバッグするような場合に役立つ。つまり、1行の中で範囲選択されている部分のみにブレークポイントを設定する機能だ。[カーソル行の前まで実行]はその名の通り、カーソル行の前までを実行してくれる。ステップ実行を1行ずつ行うのではなく、一気に実行してくれる機能だ。
以下に「body」を選択した状態で[デバッグ: 評価]と[デバッグ: ウォッチに追加]を選択した状態のVS Codeの画面を示す。
次にデバッグ用のツールバーと[デバッグ]ビューについて見ていく。
デバッグ用のツールバーでは、中断したプログラムのステップ実行が可能だ。
このツールバーでは以下の操作が可能だ(加えて、左のグラブハンドルをドラッグ&ドロップすることで、ツールバーを左右に移動できる)。
[デバッグ]ビューには現在実行している行周辺で使われている変数やパラメーターの値、ユーザーが指定の式の評価結果(ウォッチ式)、関数やメソッドのコールスタック、ブレークポイントが表示される。
(1)の[変数]ペーンには現在のスコープに導入されている変数がカテゴリーごとに表示される(前述の通り、エディタ画面でカーソルを合わせることで、特定の変数やパラメーターの値をポップアップ表示することも可能だ)。例えば、[ローカル]の下には現在のローカルスコープで定義されている変数が、[クロージャ]には現在のローカルスコープ外部で定義されているものが表示される。この他にも[グローバル]などのカテゴリーがある。
(2)の[ウォッチ式]ペーンには、ユーザーが追加した式の評価結果が表示される。先ほど「const tmp = JSON.parse(body);」の「body」をウォッチ式に追加したので、上の画像ではこれがこのペーンに表示されている。このペーンに直接、特定の式を追加することも可能だ。これには右側の[+]ボタンをクリックする。以下に、「tmp[0]」をウォッチ式に追加している様子を示す。
ウォッチ式に「tmp[0]」を追加しているところ
なお、[+]ボタンの隣にある2つのボタンはそれぞれ[すべて折りたたむ]ボタンと[すべての式を削除する]ボタンだ。前者は式の評価結果が階層構造を持っている場合に、それを折りたたんで表示するために使用する。例えば、上の画像の最後ではtmp[0]の値を展開して表示しているが、[すべて折りたたむ]ボタンをクリックすると、これが折りたたまれる。後者は文字通り、全てのウォッチ式を削除するものだ。
(3)の[コール スタック]ペーンには現在の実行されている関数/メソッドが呼び出されるまでの関数/メソッドの呼び出し経路が表示される。どういう呼び出し履歴をたどって、現在のコードが表示されているかはデバッグには欠かせない情報だ。
(4)の[ブレークポイント]ペーンには、既に述べたように現在設定されているブレークポイントが一覧表示される。なお、ブレークポイントには条件を付加することも可能だ。これには[ブレークポイント]ペーンではなく、エディタ画面にあるブレークポイントを右クリックして、コンテキストメニューから[ブレークポイントの編集]を選択する。
これにより、エディタ画面に条件を入力するボックスが表示される。
[式]を入力すると、プログラマーが指定したその式がtrueとなった場合にデバッグ実行が中断される。[ヒット カウント]を指定すると、その行が指定された回数だけ実行された時点でデバッグ実行が中断される。
これでデバッグ実行に関連するUI要素の説明が終わったので、実際にデバッグ実行を進めてみよう。
デバッグ実行の前にもう一度簡単にこのプログラムの動作について説明をしておこう。このプログラムではFace APIにURLを渡して、そのURLで示される画像の中で顔がある位置(faceRectangle)を取得するgetFaceRectangle関数と、その関数で得られた「顔がある位置の情報」を利用してEmotion APIを呼び出し、その位置にある顔の表情を取得するgetEmotion関数を定義している。実際には、それぞれの関数は今述べた処理をラップするPromiseオブジェクトを返送するので、これらをthenメソッドのチェーンでつなげるようにしている。また、ブレークポイントを仕掛けたのは、Face APIの呼び出し結果が戻されたときに実行するコールバック関数と、Emotion APIを呼び出す直前の2箇所だった。
以上を前提として、デバッグ実行を開始すると、Face API呼び出し結果を処理するコールバック関数内のブレークポイントで実行が中断する(ここまでは先ほど実行した通り)。ここではそこからステップ実行をしながら、デバッグを進めてみよう。
まずはFace APIの呼び出し結果がどうなっているかを確認してみる。これにはコールバック関数のbodyパラメーターにカーソルを合わせてその値をポップアップ表示させるか、[デバッグ]ビューの[変数]ペーンに表示されているこのパラメーターの値を確認する(下の画像の赤枠内)。
JSON形式の文字列が返送されていて、それが配列となっていて、その要素がfaceIdプロパティとfaceRectangleプロパティを持っていることが分かる。これをJSON.parseメソッドでJavaScriptオブジェクト化しようというのが、現在実行が中断されている「const tmp = JSON.parse(body);」行で行っていることだ([デバッグ]ビューでは定数tmpが「undefined」となっている点にも注意)。
この行は実行前なので、定数tmpは未初期化状態である。そこで、[ステップオーバー]ボタンを押して、実行を進めてみよう。すると、定数tmpに値が代入され、[デバッグ]ビューの表示やポップアップ表示の内容がこれを反映するものになる(下の画像の赤枠内)。
[デバッグ]ビューの[ウォッチ式]ペーンに追加した「tmp[0]」の値も変わっている点に注意しよう。次に呼び出すEmotion APIではfaceRectangleプロパティの値だけあればよいので、これを切り出して(「tmp[0].faceRectangle」と決め打ちなのはこれがサンプルコードだからだ)、resolve関数に渡してやることで、thenメソッドチェーンでこれを利用できるようにしている。必要な情報がセットアップされたことが分かったので、[続行]ボタンをクリックして、デバッグ実行を続行させる。
すると、今度はもう1つのブレークポイントでデバッグ実行が中断される。faceRectangleプロパティの値は、getEmotion関数のrectパラメーターに渡されているので、今度はこの値を確認してみよう(下の画像の赤枠内)。
[デバッグ]ビューにある[変数]ペーンの[クロージャ]の下にあるrectパラメーターの値やエディタ画面でのrectパラメーターの値のポップアップ表示を見れば分かるように、faceRectangleプロパティの値が正しく渡されていることが分かる。Emotion API呼び出しでは、この値をリクエストURLのクエリパラメーターとして埋め込んでいる(JavaScriptのテンプレート文字列を利用した「`?faceRectangles=${rect.left},……」という部分)。ここで[続行]ボタンをクリックすると、[デバッグ コンソール]パネルに最終的な呼び出し結果が表示される。
最後に[停止]ボタンをクリックすれば、デバッグ実行が終了する。
今回の最後にInspectorプロトコルを利用する方法を簡単に述べておく。これにはまず、デバッグの構成を行う必要がある。これには[デバッグ]ビューで以下の画像の(1)に示す['launch.json' を構成または編集してください]ボタンをクリックする。すると、(2)のようにlaunch.jsonファイルが作成され、エディタ画面に表示される。
launch.jsonファイルはデバッグ実行に関連する各種の構成情報を記述するファイルだ。このファイルに構成情報として「"protocol": "inspector"」を追加する。
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceRoot}\\index.js",
"protocol": "inspector"
}
]
}
これにより、Inspectorプロトコルが使用されるようになる。また、Node.js 8.0以降では自動的にこれが使われる。以下はNode.js 8.1.4をランタイムとしたときの[デバッグ コンソール]パネルの表示だ。
このようにlaunch.jsonファイルでは、言語/ランタイムなどに応じてさまざまな構成情報を記述できる(例えば、C#とJavaScriptではlaunch.jsonファイルに記述する情報は異なるものになる)。これについては、次回詳しく解説することにしよう。
今回は、[デバッグ]ビューをはじめとするデバッグ関連のUI、ステップ実行の方法など、VS Codeでデバッグを行うための基礎知識を見てきた。次回は構成ファイル(launch.jsonファイル)を用いたデバッグ実行の構成について取り上げる予定だ。
Copyright© Digital Advantage Corp. All Rights Reserved.
邱丞粋 險倅コ九Λ繝ウ繧ュ繝ウ繧ー