組み込みのデバッガーとChromeDevToolsを使用してNode.jsをデバッグする方法

提供:Dev Guides
移動先:案内検索

著者はCOVID-19救済基金を選択し、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

Node.js開発では、コーディングエラーをソースまでさかのぼって追跡することで、プロジェクトの過程で多くの時間を節約できます。 しかし、プログラムの複雑さが増すにつれて、これを効率的に行うことはますます難しくなります。 この問題を解決するために、開発者はデバッガーのようなツールを使用します。これは、開発者が実行中にプログラムを検査できるようにするプログラムです。 コードを1行ずつ再生し、プログラムの状態がどのように変化するかを観察することで、デバッガーはプログラムの実行状況を洞察し、バグを見つけやすくします。

プログラマーがコードのバグを追跡するために使用する一般的な方法は、プログラムの実行時にステートメントを印刷することです。 Node.jsでは、モジュールに console.log()またはconsole.debug()ステートメントを追加する必要があります。 この手法はすばやく使用できますが、手動でもあるため、拡張性が低くなり、エラーが発生しやすくなります。 この方法を使用すると、機密情報を誤ってコンソールに記録する可能性があり、悪意のあるエージェントに顧客またはアプリケーションに関する個人情報を提供する可能性があります。 一方、デバッガーは、プログラムをセキュリティの脅威にさらすことなく、プログラムで何が起こっているかを体系的に監視する方法を提供します。

デバッガーの主な機能は、監視オブジェクトとブレークポイントの追加です。 オブジェクトを監視することにより、デバッガーは、プログラマーがプログラムをステップ実行するときに変数の変更を追跡するのに役立ちます。 ブレークポイントは、開発者が調査しているポイントを超えてコードが続行されないようにするために、プログラマーがコードに配置できるマーカーです。

この記事では、デバッガーを使用して、いくつかのサンプルNode.jsアプリケーションをデバッグします。 まず、組み込みの Node.jsデバッガツールを使用してコードをデバッグし、バグの根本原因を見つけることができるようにウォッチャーとブレークポイントを設定します。 次に、コマンドラインNode.jsデバッガーの代わりに Google Chrome DevToolsグラフィカルユーザーインターフェイス(GUI)として使用します。

前提条件

  • 開発環境にNode.jsをインストールする必要があります。 このチュートリアルでは、バージョン10.19.0を使用します。 これをmacOSまたはUbuntu18.04にインストールするには、Node.jsをインストールしてmacOSにローカル開発環境を作成する方法またはのPPAを使用したインストール]セクションの手順に従います。 Ubuntu18.04にNode.jsをインストールする方法。
  • この記事では、ユーザーが基本的なJavaScript、特に関数の作成と使用に慣れていることを期待しています。 JavaScriptシリーズのコーディング方法を読むことで、これらの基礎などを学ぶことができます。
  • Chrome DevToolsデバッガーを使用するには、 GoogleChromeWebブラウザーまたはオープンソースのChromiumWebブラウザーをダウンロードしてインストールする必要があります。

ステップ1—Node.jsデバッガーでウォッチャーを使用する

デバッガーは主に2つの機能に役立ちます。変数を監視し、プログラムの実行時に変数がどのように変化するかを観察する機能と、ブレークポイントと呼ばれるさまざまな場所でコードの実行を停止および開始する機能です。 このステップでは、変数を監視してコードのエラーを特定する方法を実行します。

コードをステップ実行するときに変数を監視することで、プログラムの実行中に変数の値がどのように変化するかを知ることができます。 例を使用して、コード内の論理エラーを見つけて修正するのに役立つ変数の監視を練習しましょう。

まず、コーディング環境を設定します。 ターミナルで、debuggingという名前の新しいフォルダーを作成します。

mkdir debugging

次に、そのフォルダに入ります。

cd debugging

badLoop.jsという名前の新しいファイルを開きます。 ターミナルで利用できるnanoを使用します。

nano badLoop.js

このコードは、 array を反復処理し、合計に数値を加算します。この例では、店舗での1週間の1日の注文数を加算するために使用されます。 プログラムは、配列内のすべての数値の合計を返します。 エディターで、次のコードを入力します。

デバッグ/badLoop.js

let orders = [341, 454, 198, 264, 307];

let totalOrders = 0;

for (let i = 0; i <= orders.length; i++) {
  totalOrders += orders[i];
}

console.log(totalOrders);

まず、5つの数値を格納するorders配列を作成します。 次に、totalOrders0に初期化します。これは、5つの数値の合計を格納するためです。 forループでは、ordersの各値をtotalOrdersに繰り返し加算します。 最後に、プログラムの最後に注文の合計金額を印刷します。

保存してエディターを終了します。 次に、nodeを使用してこのプログラムを実行します。

node badLoop.js

端末に次の出力が表示されます。

OutputNaN

JavaScriptのNaNは、数値ではないを意味します。 すべての入力が有効な数値であることを考えると、これは予期しない動作です。 エラーを見つけるために、Node.jsデバッガーを使用して、forループで変更された2つの変数totalOrdersiがどうなるかを見てみましょう。

プログラムで組み込みのNode.jsデバッガーを使用する場合は、ファイル名の前にinspectを含めます。 ターミナルで、次のようにこのデバッガオプションを指定してnodeコマンドを実行します。

node inspect badLoop.js

デバッガーを起動すると、次のような出力が表示されます。

Output< Debugger listening on ws://127.0.0.1:9229/e1ebba25-04b8-410b-811e-8a0c0902717a
< For help, see: https://nodejs.org/en/docs/inspector
< Debugger attached.
Break on start in badLoop.js:1
> 1 let orders = [341, 454, 198, 264, 307];
  2 
  3 let totalOrders = 0;

最初の行は、デバッグサーバーのURLを示しています。 これは、後で説明するWebブラウザなどの外部クライアントでデバッグする場合に使用されます。 このサーバーは、デフォルトでlocalhost127.0.0.1)のポート:9229でリッスンすることに注意してください。 セキュリティ上の理由から、このポートを一般に公開しないことをお勧めします。

デバッガーが接続されると、デバッガーはBreak on start in badLoop.js:1を出力します。

ブレークポイントは、実行を停止するコード内の場所です。 デフォルトでは、Node.jsのデバッガーはファイルの先頭で実行を停止します。

次に、デバッガーはコードのスニペットを表示し、その後に特別なdebugプロンプトが続きます。

Output...
> 1 let orders = [341, 454, 198, 264, 307];
  2 
  3 let totalOrders = 0;
debug>

1の横にある>は、実行で到達した行を示し、プロンプトは、デバッガーへの表彰を入力する場所です。 この出力が表示されると、デバッガーはコマンドを受け入れる準備ができています。

デバッガーを使用する場合、プログラムが実行する次の行に移動するようにデバッガーに指示することにより、コードをステップスルーします。 Node.jsでは、次のコマンドでデバッガーを使用できます。

  • cまたはcont:次のブレークポイントまたはプログラムの最後まで実行を続行します。
  • nまたはnext:コードの次の行に移動します。
  • sまたはstep:関数にステップインします。 デフォルトでは、デバッグしているブロックまたはscopeのコードのみをステップスルーします。 関数にステップインすることで、コードが呼び出す関数のコードを検査し、それがデータにどのように反応するかを観察できます。
  • o:関数からステップアウトします。 関数にステップインした後、関数が戻ると、デバッガーはメインファイルに戻ります。 このコマンドを使用して、関数の実行が完了する前にデバッグしていた元の関数に戻ることができます。
  • pause:実行中のコードを一時停止します。

このコードを1行ずつ説明します。 nを押して、次の行に移動します。

n

これで、デバッガーはコードの3行目でスタックします。

Outputbreak in badLoop.js:3
  1 let orders = [341, 454, 198, 264, 307];
  2 
> 3 let totalOrders = 0;
  4 
  5 for (let i = 0; i <= orders.length; i++) {

便宜上、空の行はスキップされます。 デバッグコンソールでもう一度nを押すと、デバッガーはコードの5行目に配置されます。

Outputbreak in badLoop.js:5
  3 let totalOrders = 0;
  4 
> 5 for (let i = 0; i <= orders.length; i++) {
  6   totalOrders += orders[i];
  7 }

現在、ループを開始しています。 端末がカラーをサポートしている場合、let i = 00が強調表示されます。 デバッガーは、プログラムが実行しようとしているコードの部分を強調表示し、forループでは、カウンターの初期化が最初に実行されます。 ここから、totalOrdersが数値ではなくNaNを返す理由を確認できます。 このループでは、反復ごとにtotalOrdersiの2つの変数が変更されます。 これらの変数の両方にウォッチャーを設定しましょう。

まず、totalOrders変数のウォッチャーを追加します。 インタラクティブシェルで、次のように入力します。

watch('totalOrders')

変数を監視するには、組み込みのwatch()関数を使用し、変数名を含むstring引数を指定します。 watch()機能でENTERを押すと、プロンプトはフィードバックを提供せずに次の行に移動しますが、デバッガーを次の行に移動するとウォッチワードが表示されます。

次に、変数iのウォッチャーを追加しましょう。

watch('i')

これで、ウォッチャーの動作を確認できます。 nを押して、次の手順に進みます。 デバッグコンソールには次のように表示されます。

Outputbreak in badLoop.js:5
Watchers:
  0: totalOrders = 0
  1: i = 0

  3 let totalOrders = 0;
  4 
> 5 for (let i = 0; i <= orders.length; i++) {
  6   totalOrders += orders[i];
  7 }

デバッガーは、出力に示されているように、コード行を表示する前にtotalOrdersiの値を表示するようになりました。 これらの値は、コード行によって変更されるたびに更新されます。

この時点で、デバッガーはorders.lengthlengthを強調表示しています。 これは、プログラムがブロック内のコードを実行する前に、条件をチェックしようとしていることを意味します。 コードが実行された後、最終式i++が実行されます。 forループとその実行について詳しくは、JavaScriptでForループを作成する方法ガイドをご覧ください。

コンソールにnと入力して、forループの本体に入ります。

Outputbreak in badLoop.js:6
Watchers:
  0: totalOrders = 0
  1: i = 0

  4 
  5 for (let i = 0; i <= orders.length; i++) {
> 6   totalOrders += orders[i];
  7 }
  8 

このステップでは、totalOrders変数を更新します。 したがって、このステップが完了すると、変数とウォッチャーが更新されます。

nを押して確認します。 あなたはこれを見るでしょう:

OutputWatchers:
  0: totalOrders = 341
  1: i = 0

  3 let totalOrders = 0;
  4 
> 5 for (let i = 0; i <= orders.length; i++) {
  6   totalOrders += orders[i];
  7 }

強調表示されているように、totalOrdersは1次の値341になります。

デバッガーは、ループの最終条件を処理しようとしています。 nと入力して、この行を実行し、iを更新します。

Outputbreak in badLoop.js:5
Watchers:
  0: totalOrders = 341
  1: i = 1

  3 let totalOrders = 0;
  4 
> 5 for (let i = 0; i <= orders.length; i++) {
  6   totalOrders += orders[i];
  7 }

初期化後、変数が更新されるのを確認するために、コードを4回ステップ実行する必要がありました。 このようにコードをステップ実行するのは面倒な場合があります。 この問題は、ステップ2のブレークポイントで対処されます。 しかし今のところ、ウォッチャーを設定することで、ウォッチャーの価値を観察し、問題を見つける準備ができています。

nをさらに12回入力して、出力を確認しながら、プログラムをステップ実行します。 コンソールには次のように表示されます。

Outputbreak in badLoop.js:5
Watchers:
  0: totalOrders = 1564
  1: i = 5

  3 let totalOrders = 0;
  4 
> 5 for (let i = 0; i <= orders.length; i++) {
  6   totalOrders += orders[i];
  7 }

orders配列には5つのアイテムがあり、i5の位置にあることを思い出してください。 ただし、iは配列のインデックスとして使用されるため、orders[5]には値がありません。 orders配列の最後の値は、インデックス4にあります。 これは、orders[5]の値がundefinedになることを意味します。

コンソールにnと入力すると、ループ内のコードが実行されていることがわかります。

Outputbreak in badLoop.js:6
Watchers:
  0: totalOrders = 1564
  1: i = 5

  4 
  5 for (let i = 0; i <= orders.length; i++) {
> 6   totalOrders += orders[i];
  7 }
  8 

nともう一度入力すると、その反復後のtotalOrdersの値が表示されます。

Outputbreak in badLoop.js:5
Watchers:
  0: totalOrders = NaN
  1: i = 5

  3 let totalOrders = 0;
  4 
> 5 for (let i = 0; i <= orders.length; i++) {
  6   totalOrders += orders[i];
  7 }

totalOrdersiをデバッグおよび監視すると、ループが5回ではなく6回繰り返されていることがわかります。 i5の場合、orders[5]totalOrdersに追加されます。 orders[5]undefinedであるため、これを数値に加算するとNaNになります。 したがって、コードの問題はforループの条件にあります。 iorders配列の長さ以下であるかどうかをチェックする代わりに、長さよりも短いことだけをチェックする必要があります。

デバッガーを終了し、変更を加えて、コードを再実行しましょう。 デバッグプロンプトで、exitコマンドを入力し、ENTERを押します。

.exit

デバッガーを終了したので、テキストエディターでbadLoop.jsを開きます。

nano badLoop.js

forループの条件を変更します。

デバッガー/badLoop.js

...
for (let i = 0; i < orders.length; i++) {
...

nanoを保存して終了します。 次に、次のようにスクリプトを実行してみましょう。

node badLoop.js

完了すると、正しい結果が出力されます。

Output1564

このセクションでは、デバッガーのwatchコマンドを使用してコードのバグを見つけ、修正し、期待どおりに機能することを確認しました。

デバッガーを使用して変数を監視する基本的な経験ができたので、プログラムの最初からすべてのコード行をステップ実行せずにデバッグできるように、ブレークポイントを使用する方法を見てみましょう。

ステップ2—Node.jsデバッガーでブレークポイントを使用する

Node.jsプロジェクトは、相互接続された多くのモジュールで構成されるのが一般的です。 特にアプリの複雑さが増すにつれて、各モジュールを1行ずつデバッグするのは時間がかかります。 この問題を解決するために、ブレークポイントを使用すると、実行を一時停止してプログラムを検査するコード行にジャンプできます。

Node.jsでデバッグする場合、debuggerキーワードをコードに直接追加してブレークポイントを追加します。 次に、デバッガコンソールでnの代わりにcを押すと、あるブレークポイントから次のブレークポイントに移動できます。 各ブレークポイントで、関心のある表現のウォッチャーを設定できます。

これを例で見てみましょう。 このステップでは、文のリストを読み取り、すべてのテキストで使用される最も一般的な単語を判別するプログラムをセットアップします。 サンプルコードは、出現回数が最も多い最初の単語を返します。

この演習では、3つのファイルを作成します。 最初のファイルsentences.txtには、プログラムが処理する生データが含まれています。 Encyclopaedia Britannicaのジンベイザメに関する記事の冒頭のテキストを、句読点を削除してサンプルデータとして追加します。

テキストエディタでファイルを開きます。

nano sentences.txt

次に、次のコードを入力します。

デバッガー/sentences.txt

Whale shark Rhincodon typus gigantic but harmless shark family Rhincodontidae that is the largest living fish
Whale sharks are found in marine environments worldwide but mainly in tropical oceans
They make up the only species of the genus Rhincodon and are classified within the order Orectolobiformes a group containing the carpet sharks
The whale shark is enormous and reportedly capable of reaching a maximum length of about 18 metres 59 feet
Most specimens that have been studied however weighed about 15 tons about 14 metric tons and averaged about 12 metres 39 feet in length
The body coloration is distinctive
Light vertical and horizontal stripes form a checkerboard pattern on a dark background and light spots mark the fins and dark areas of the body

ファイルを保存して終了します。

それでは、コードをtextHelper.jsに追加しましょう。 このモジュールには、テキストファイルの処理に使用するいくつかの便利な関数が含まれているため、最も人気のある単語を簡単に判別できます。 テキストエディタでtextHelper.jsを開きます。

nano textHelper.js

sentences.txtのデータを処理するための3つの関数を作成します。 1つ目は、ファイルを読み取ることです。 textHelper.jsに次のように入力します。

デバッガー/textHelper.js

const fs = require('fs');

const readFile = () => {
  let data = fs.readFileSync('sentences.txt');
  let sentences = data.toString();
  return sentences;
};

まず、 fs Node.jsライブラリをインポートして、ファイルを読み取れるようにします。 次に、readFileSync()を使用してsentences.txtからバッファオブジェクトおよびtoString()メソッドとしてデータをロードするreadFile()関数を作成します。文字列として返します。

追加する次の関数は、テキストの文字列を処理し、その単語を含む配列にフラット化します。 次のコードをエディターに追加します。

textHelper.js

...

const getWords = (text) => {
  let allSentences = text.split('\n');
  let flatSentence = allSentences.join(' ');
  let words = flatSentence.split(' ');
  words = words.map((word) => word.trim().toLowerCase());
  return words;
};

このコードでは、メソッド split()join()、および map()を使用して、文字列を個々の単語の配列に操作しています。 また、この関数は、カウントを容易にするために各単語を小文字にします。

必要な最後の関数は、文字列配列内のさまざまな単語の数を返します。 次のような最後の関数を追加します。

デバッガー/textHelper.js

...

const countWords = (words) => {
  let map = {};
  words.forEach((word) => {
    if (word in map) {
      map[word] = 1;
    } else {
      map[word] += 1;
    }
  });

  return map;
};

ここでは、JavaScriptオブジェクトというmapを作成します。このオブジェクトは、単語をキーとして、カウントを値として使用します。 配列をループし、ループの現在の要素である場合は、各単語の数に1を追加します。 これらの関数をエクスポートして、他のモジュールで使用できるようにして、このモジュールを完成させましょう。

デバッガー/textHelper.js

...

module.exports = { readFile, getWords, countWords };

保存して終了。

この演習で使用する3番目の最後のファイルでは、textHelper.jsモジュールを使用して、テキスト内で最も人気のある単語を検索します。 テキストエディタでindex.jsを開きます。

nano index.js

textHelpers.jsモジュールをインポートすることからコードを開始します。

デバッガー/index.js

const textHelper = require('./textHelper');

ストップワードを含む新しいアレイを作成して続行します。

デバッガー/index.js

...

const stopwords = ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what', 'which', 'who', 'whom', 'this', 'that', 'these', 'those', 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can', 'will', 'just', 'don', 'should', 'now', ''];

ストップワードは、テキストを処理する前に除外する言語で一般的に使用される単語です。 これを使用して、英語のテキストで最も人気のある単語がtheまたはaであるという結果よりも意味のあるデータを見つけることができます。

textHelper.jsモジュール関数を使用して続行し、単語とその数を含むJavaScriptオブジェクトを取得します。

デバッガー/index.js

...

let sentences = textHelper.readFile();
let words = textHelper.getWords(sentences);
let wordCounts = textHelper.countWords(words);

次に、最も頻度の高い単語を判別することにより、このモジュールを完了できます。 これを行うには、オブジェクトの各キーを単語数でループし、その数を以前に保存された最大値と比較します。 単語の数が多い場合は、新しい最大値になります。

次のコード行を追加して、最も人気のある単語を計算します。

デバッガー/index.js

...

let max = -Infinity;
let mostPopular = '';

Object.entries(wordCounts).forEach(([word, count]) => {
  if (stopwords.indexOf(word) === -1) {
    if (count > max) {
      max = count;
      mostPopular = word;
    }
  }
});

console.log(`The most popular word in the text is "${mostPopular}" with ${max} occurrences`);

このコードでは、 Object.entries()を使用して、wordCountsオブジェクトのキーと値のペアを個々の配列に変換しています。これらはすべて、より大きな配列内にネストされています。 次に、 forEach()メソッドといくつかの条件付きステートメントを使用して、各単語の数をテストし、最大数を格納します。

ファイルを保存して終了します。

このファイルを実行して、実際の動作を確認してみましょう。 端末で次のコマンドを入力します。

node index.js

次の出力が表示されます。

OutputThe most popular word in the text is "whale" with 1 occurrences

テキストを読むと、答えが間違っていることがわかります。 sentences.txtをすばやく検索すると、whaleという単語が複数回表示されていることがわかります。

このエラーを引き起こす可能性のある関数がかなりあります。ファイル全体を読み取っていないか、テキストを配列とJavaScriptオブジェクトに正しく処理していない可能性があります。 最大の単語を見つけるためのアルゴリズムも正しくない可能性があります。 何が悪いのかを理解する最良の方法は、デバッガーを使用することです。

大規模なコードベースがなくても、コードの各行をステップスルーして、状況がいつ変化するかを観察することに時間を費やしたくありません。 代わりに、ブレークポイントを使用して、関数が戻る前にそれらの重要な瞬間に移動し、出力を観察できます。

textHelper.jsモジュールの各関数にブレークポイントを追加しましょう。 そのためには、コードにキーワードdebuggerを追加する必要があります。

textHelper.jsファイルをテキストエディタで開きます。 もう一度nanoを使用します。

nano textHelper.js

まず、次のようにreadFile()関数にブレークポイントを追加します。

デバッガー/textHelper.js

...

const readFile = () => {
  let data = fs.readFileSync('sentences.txt');
  let sentences = data.toString();
  debugger;
  return sentences;
};

...

次に、getWords()関数に別のブレークポイントを追加します。

デバッガー/textHelper.js

...

const getWords = (text) => {
  let allSentences = text.split('\n');
  let flatSentence = allSentences.join(' ');
  let words = flatSentence.split(' ');
  words = words.map((word) => word.trim().toLowerCase());
  debugger;
  return words;
};

...

最後に、countWords()関数にブレークポイントを追加します。

デバッガー/textHelper.js

...

const countWords = (words) => {
  let map = {};
  words.forEach((word) => {
    if (word in map) {
      map[word] = 1;
    } else {
      map[word] += 1;
    }
  });

  debugger;
  return map;
};

...

textHelper.jsを保存して終了します。

デバッグプロセスを始めましょう。 ブレークポイントはtextHelpers.jsにありますが、アプリケーションの主要なエントリポイントであるindex.jsをデバッグしています。 シェルで次のコマンドを入力して、デバッグセッションを開始します。

node inspect index.js

コマンドを入力すると、次の出力が表示されます。

Output< Debugger listening on ws://127.0.0.1:9229/b2d3ce0e-3a64-4836-bdbf-84b6083d6d30
< For help, see: https://nodejs.org/en/docs/inspector
< Debugger attached.
Break on start in index.js:1
> 1 const textHelper = require('./textHelper');
  2 
  3 const stopwords = ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what', 'which', 'who', 'whom', 'this', 'that', 'these', 'those', 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can', 'will', 'just', 'don', 'should', 'now', ''];

今回は、インタラクティブデバッガにcと入力します。 念のため、cはcontinueの略です。 これにより、デバッガーがコード内の次のブレークポイントにジャンプします。 cを押してENTERと入力すると、コンソールに次のように表示されます。

Outputbreak in textHelper.js:6
  4   let data = fs.readFileSync('sentences.txt');
  5   let sentences = data.toString();
> 6   debugger;
  7   return sentences;
  8 };

ブレークポイントに直接移動することで、デバッグ時間を節約できました。

この関数では、ファイル内のすべてのテキストが返されることを確認する必要があります。 sentences変数のウォッチャーを追加して、何が返されるかを確認できるようにします。

watch('sentences')

nを押してコードの次の行に移動し、sentencesの内容を確認できるようにします。 次の出力が表示されます。

Outputbreak in textHelper.js:7
Watchers:
  0: sentences =
    'Whale shark Rhincodon typus gigantic but harmless shark family Rhincodontidae that is the largest living fish\n' +
      'Whale sharks are found in marine environments worldwide but mainly in tropical oceans\n' +
      'They make up the only species of the genus Rhincodon and are classified within the order Orectolobiformes a group containing the carpet sharks\n' +
      'The whale shark is enormous and reportedly capable of reaching a maximum length of about 18 metres 59 feet\n' +
      'Most specimens that have been studied however weighed about 15 tons about 14 metric tons and averaged about 12 metres 39 feet in length\n' +
      'The body coloration is distinctive\n' +
      'Light vertical and horizontal stripes form a checkerboard pattern on a dark background and light spots mark the fins and dark areas of the body\n'

  5   let sentences = data.toString();
  6   debugger;
> 7   return sentences;
  8 };
  9

ファイルの読み取りに問題はないようです。 問題はコードの他の場所にあるはずです。 cをもう一度押して、次のブレークポイントに移動しましょう。 実行すると、次の出力が表示されます。

Outputbreak in textHelper.js:15
Watchers:
  0: sentences =
    ReferenceError: sentences is not defined
        at eval (eval at getWords (your_file_path/debugger/textHelper.js:15:3), <anonymous>:1:1)
        at Object.getWords (your_file_path/debugger/textHelper.js:15:3)
        at Object.<anonymous> (your_file_path/debugger/index.js:7:24)
        at Module._compile (internal/modules/cjs/loader.js:1125:14)
        at Object.Module._extensions..js (internal/modules/cjs/loader.js:1167:10)
        at Module.load (internal/modules/cjs/loader.js:983:32)
        at Function.Module._load (internal/modules/cjs/loader.js:891:14)
        at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
        at internal/main/run_main_module.js:17:47

 13   let words = flatSentence.split(' ');
 14   words = words.map((word) => word.trim().toLowerCase());
>15   debugger;
 16   return words;
 17 };

sentences変数のウォッチャーを設定したため、このエラーメッセージが表示されますが、その変数は現在の関数スコープに存在しません。 ウォッチャーはデバッグセッション全体にわたって存続するため、定義されていないsentencesを監視し続ける限り、このエラーが引き続き表示されます。

unwatch()コマンドを使用して、変数の監視を停止できます。 sentencesの監視を解除して、デバッガーが出力を出力するたびにこのエラーメッセージを表示する必要がなくなるようにします。 対話型プロンプトで、次のコマンドを入力します。

unwatch('sentences')

変数の監視を解除しても、デバッガーは何も出力しません。

getWords()関数に戻って、前にロードしたテキストから取得した単語のリストを確実に返すようにします。 words変数の値を見てみましょう。

watch('words')

次に、nと入力してデバッガーの次の行に移動し、wordsに何が格納されているかを確認します。 デバッガーは次のように表示します。

Outputbreak in textHelper.js:16
Watchers:
  0: words =
    [ 'whale',
      'shark',
      'rhincodon',
      'typus',
      'gigantic',
      'but',
      'harmless',
      ...
      'metres',
      '39',
      'feet',
      'in',
      'length',
      '',
      'the',
      'body',
      'coloration',
      ... ]

 14   words = words.map((word) => word.trim().toLowerCase());
 15   debugger;
>16   return words;
 17 };
 18

デバッガーは配列全体を出力しません。これは非常に長く、出力が読みにくくなるためです。 ただし、出力は、何を格納する必要があるかという私たちの期待を満たしています。sentencesからのテキストは小文字の文字列に分割されます。 getWords()は正常に機能しているようです。

countWords()関数の観察に移りましょう。 まず、words配列の監視を解除して、次のブレークポイントにいるときにデバッガーエラーが発生しないようにします。 コマンドプロンプトで、次のように入力します。

unwatch('words')

次に、プロンプトにcと入力します。 最後のブレークポイントで、これがシェルに表示されます。

Outputbreak in textHelper.js:29
 27   });
 28 
>29   debugger;
 30   return map;
 31 };

この関数では、map変数に文の各単語の数が正しく含まれていることを確認する必要があります。 まず、map変数を監視するようにデバッガーに指示しましょう。

watch('map')

nを押して、次の行に移動します。 デバッガーはこれを表示します:

Outputbreak in textHelper.js:30
Watchers:
  0: map =
    { 12: NaN,
      14: NaN,
      15: NaN,
      18: NaN,
      39: NaN,
      59: NaN,
      whale: 1,
      shark: 1,
      rhincodon: 1,
      typus: NaN,
      gigantic: NaN,
      ... }

 28
 29   debugger;
>30   return map;
 31 };
 32

それは正しく見えません。 単語を数える方法が間違った結果を生み出しているようです。 これらの値が入力されている理由がわからないため、次のステップは、words配列で使用されるループで何が起こっているかをデバッグすることです。 これを行うには、ブレークポイントを配置する場所にいくつかの変更を加える必要があります。

まず、デバッグコンソールを終了します。

.exit

テキストエディタでtextHelper.jsを開き、ブレークポイントを編集できるようにします。

nano textHelper.js

まず、readFile()getWords()が機能していることを確認して、それらのブレークポイントを削除します。 次に、関数の最後からcountWords()のブレークポイントを削除し、forEach()ブロックの最初と最後に2つの新しいブレークポイントを追加します。

textHelper.jsを編集して、次のようにします。

デバッガー/textHelper.js

...

const readFile = () => {
  let data = fs.readFileSync('sentences.txt');
  let sentences = data.toString();
  return sentences;
};

const getWords = (text) => {
  let allSentences = text.split('\n');
  let flatSentence = allSentences.join(' ');
  let words = flatSentence.split(' ');
  words = words.map((word) => word.trim().toLowerCase());
  return words;
};

const countWords = (words) => {
  let map = {};
  words.forEach((word) => {
    debugger;
    if (word in map) {
      map[word] = 1;
    } else {
      map[word] += 1;
    }
    debugger;
  });

  return map;
};

...

CTRL+Xを使用してnanoを保存して終了します。

次のコマンドを使用して、デバッガーを再起動してみましょう。

node inspect index.js

何が起こっているのかを洞察するために、ループ内のいくつかのことをデバッグしたいと思います。 まず、wordのウォッチャーを設定しましょう。これは、ループが現在調べている文字列を含むforEach()ループで使用される引数です。 デバッグプロンプトで、次のように入力します。

watch('word')

これまでのところ、変数のみを監視してきました。 しかし、時計は変数に限定されません。 コードで使用されている有効なJavaScript式を監視できます。

実際には、条件word in mapのウォッチャーを追加できます。これにより、数値のカウント方法が決まります。 デバッグプロンプトで、次のウォッチャーを作成します。

watch('word in map')

map変数で変更されている値のウォッチャーも追加しましょう。

watch('map[word]')

ウォッチャーは、コードでは使用されていないが、コードで評価できる式にすることもできます。 word変数の長さのウォッチャーを追加して、これがどのように機能するかを見てみましょう。

watch('word.length')

すべてのウォッチャーを設定したので、デバッガープロンプトにcと入力して、countWords()のループの最初の要素がどのように評価されるかを確認します。 デバッガーは次の出力を出力します。

Outputbreak in textHelper.js:20
Watchers:
  0: word = 'whale'
  1: word in map = false
  2: map[word] = undefined
  3: word.length = 5

 18   let map = {};
 19   words.forEach((word) => {
>20     debugger;
 21     if (word in map) {
 22       map[word] = 1;

ループの最初の単語はwhaleです。 この時点で、mapオブジェクトには、whaleが空のキーがありません。 続いて、mapwhaleを検索すると、undefinedが表示されます。 最後に、whaleの長さは5です。 これは問題のデバッグには役立ちませんが、デバッグ中にコードで評価できる式を監視できることを検証します。

cをもう一度押して、ループの終わりまでに何が変更されたかを確認します。 デバッガーはこれを表示します:

Outputbreak in textHelper.js:26
Watchers:
  0: word = 'whale'
  1: word in map = true
  2: map[word] = NaN
  3: word.length = 5

 24       map[word] += 1;
 25     }
>26     debugger;
 27   });
 28

map変数にwhaleキーが含まれているため、ループの最後でword in mapがtrueになります。 whaleキーのmapの値はNaNであり、これは問題を浮き彫りにします。 countWords()ifステートメントは、単語のカウントが新しい場合は1に設定し、既存の場合は1を追加することを目的としています。

犯人はifステートメントの状態です。 mapwordが見つからない場合は、map[word]1に設定する必要があります。 現在、wordが見つかった場合は、1つ追加しています。 ループの開始時、map["whale"]undefinedです。 JavaScriptでは、undefined + 1は数値ではなくNaNと評価されます。

これを修正するには、!演算子を使用して[をテストし、ifステートメントの条件を(word in map)から(!(word in map))に変更します。 X150X]はmapにはありません。 countWords()関数にその変更を加えて、何が起こるかを見てみましょう。

まず、デバッガーを終了します。

.exit

次に、テキストエディタでtextHelper.jsファイルを開きます。

nano textHelper.js

countWords()関数を次のように変更します。

デバッガー/textHelper.js

...

const countWords = (words) => {
  let map = {};
  words.forEach((word) => {
    if (!(word in map)) {
      map[word] = 1;
    } else {
      map[word] += 1;
    }
  });

  return map;
};

...

エディターを保存して閉じます。

それでは、デバッガーなしでこのファイルを実行してみましょう。 ターミナルで、次のように入力します。

node index.js

スクリプトは次の文を出力します。

OutputThe most popular word in the text is "whale" with 3 occurrences

この出力は、以前に受け取ったものよりもはるかに可能性が高いようです。 デバッガーを使用して、問題の原因となった関数と発生しなかった関数を特定しました。

組み込みのCLIデバッガーを使用して2つの異なるNode.jsプログラムをデバッグしました。 debuggerキーワードを使用してブレークポイントを設定し、内部状態の変化を監視するさまざまなウォッチャーを作成できるようになりました。 ただし、GUIアプリケーションからコードをより効果的にデバッグできる場合もあります。

次のセクションでは、GoogleChromeのDevToolsのデバッガーを使用します。 Node.jsでデバッガーを起動し、Google Chromeの専用のデバッグページに移動し、GUIを使用してブレークポイントとウォッチャーを設定します。

ステップ3—ChromeDevToolsを使用したNode.jsのデバッグ

Chrome DevToolsは、WebブラウザーでNode.jsをデバッグするための一般的な選択肢です。 Node.jsはChromeが使用するのと同じV8JavaScriptエンジンを使用するため、デバッグエクスペリエンスは他のデバッガーよりも統合されています。

この演習では、HTTPサーバーを実行してJSON応答を返す新しいNode.jsアプリケーションを作成します。 次に、デバッガーを使用してブレークポイントを設定し、要求に対して生成されている応答についてより深い洞察を得ます。

サーバーコードを保存するserver.jsという新しいファイルを作成しましょう。 テキストエディタでファイルを開きます。

nano server.js

このアプリケーションは、Hello Worldの挨拶を含むJSONを返します。 さまざまな言語のメッセージの配列があります。 リクエストを受信すると、ランダムに挨拶を選び、JSON本文で返します。

このアプリケーションは、ポート:8000localhostサーバーで実行されます。 Node.jsを使用したHTTPサーバーの作成について詳しく知りたい場合は、 HTTPModuleを使用してNode.jsでWebサーバーを作成する方法に関するガイドをお読みください。

次のコードをテキストエディタに入力します。

デバッガー/server.js

const http = require("http");

const host = 'localhost';
const port = 8000;

const greetings = ["Hello world", "Hola mundo", "Bonjour le monde", "Hallo Welt", "Salve mundi"];

const getGreeting = function () {
  let greeting = greetings[Math.floor(Math.random() * greetings.length)];
  return greeting
}

まず、HTTPサーバーの作成に必要なhttpモジュールをインポートします。 次に、host変数とport変数を設定します。これらの変数は、後でサーバーを実行するために使用します。 greetings配列には、サーバーが返す可能性のあるすべてのグリーティングが含まれています。 getGreeting()関数は、あいさつをランダムに選択して返します。

HTTPリクエストを処理するリクエストリスナーを追加し、サーバーを実行するためのコードを追加しましょう。 次のように入力して、Node.jsモジュールの編集を続けます。

デバッガー/server.js

...

const requestListener = function (req, res) {
  let message = getGreeting();
  res.setHeader("Content-Type", "application/json");
  res.writeHead(200);
  res.end(`{"message": "${message}"}`);
};

const server = http.createServer(requestListener);
server.listen(port, host, () => {
  console.log(`Server is running on http://${host}:${port}`);
});

これでサーバーを使用する準備ができたので、Chromeデバッガーをセットアップしましょう。

次のコマンドでChromeデバッガーを起動できます。

node --inspect server.js

注:CLIデバッガーとChromeデバッガーのコマンドの違いに注意してください。 CLIを使用する場合は、inspectを使用します。 Chromeを使用する場合は、--inspectを使用します。


デバッガーを起動すると、次の出力が表示されます。

OutputDebugger listening on ws://127.0.0.1:9229/996cfbaf-78ca-4ebd-9fd5-893888efe8b3
For help, see: https://nodejs.org/en/docs/inspector
Server is running on http://localhost:8000

次に、GoogleChromeまたはChromium を開き、アドレスバーにchrome://inspectと入力します。 Microsoft EdgeもV8JavaScriptエンジンを使用しているため、同じデバッガーを使用できます。 Microsoft Edgeを使用している場合は、edge://inspectに移動します。

URLに移動すると、次のページが表示されます。

Devices ヘッダーの下で、ノード専用のDevToolsを開くボタンをクリックします。 新しいウィンドウがポップアップ表示されます。

これで、ChromeでNode.jsコードをデバッグできるようになりました。 ソースタブがまだ表示されていない場合は、そこに移動します。 左側で、ファイルツリーを展開し、server.jsを選択します。

コードにブレークポイントを追加しましょう。 サーバーが挨拶を選択し、それを返そうとしているときに停止したいと思います。 デバッグコンソールで行番号10をクリックします。 番号の横に赤い点が表示され、右側のパネルに新しいブレークポイントが追加されたことを示します。

次に、ウォッチ式を追加しましょう。 右側のパネルで、 Watch ヘッダーの横にある矢印をクリックしてウォッチワードリストを開き、+をクリックします。 greetingと入力し、ENTERを押すと、リクエストの処理時にその値を確認できます。

次に、コードをデバッグしましょう。 新しいブラウザウィンドウを開き、http://localhost:8000(Node.jsサーバーが実行されているアドレス)に移動します。 ENTERを押しても、すぐには応答がありません。 代わりに、デバッグウィンドウがもう一度ポップアップ表示されます。 すぐにフォーカスが合わない場合は、デバッグウィンドウに移動して次のことを確認してください。

デバッガーは、ブレークポイントを設定したサーバーの応答を一時停止します。 監視している変数は、右側のパネルと、それを作成したコード行で更新されます。

ブレークポイントで一時停止のすぐ上にある右側のパネルの続行ボタンを押して、応答の実行を完了しましょう。 応答が完了すると、Node.jsサーバーとの通信に使用されるブラウザーウィンドウに成功したJSON応答が表示されます。

{"message": "Hello world"}

このように、Chrome DevToolsは、ブレークポイントを追加するためにコードを変更する必要はありません。 コマンドラインでグラフィカルアプリケーションを使用してデバッグする場合は、ChromeDevToolsの方が適しています。

結論

この記事では、アプリケーションの状態を監視するウォッチャーを設定し、プログラムの実行のさまざまな時点で実行を一時停止できるようにブレークポイントを追加することで、サンプルのNode.jsアプリケーションをデバッグしました。 これは、組み込みのCLIデバッガーとGoogleChromeのDevToolsの両方を使用して実現しました。

多くのNode.js開発者は、コードをデバッグするためにコンソールにログオンします。 これは便利ですが、実行を一時停止してさまざまな状態の変化を監視できるほど柔軟ではありません。 このため、デバッグツールを使用する方が効率的であることが多く、プロジェクトの開発中に時間を節約できます。

これらのデバッグツールの詳細については、Node.jsのドキュメントまたはChromeDevToolsのドキュメントをご覧ください。 Node.jsの学習を継続したい場合は、 Node.jsシリーズのコーディング方法に戻るか、Nodeトピックページでプログラミングプロジェクトとセットアップを参照してください。